Author SHA1 Message Date
Evgeny a80ba30b06 test_etcp_router_unit: fix FAIL macro — bare return in non-void function 2026-06-01 10:39:33 +03:00
Evgeny ed6b4d1046 debug: выделить link keepalive в отдельную категорию KEEPALIVE (21), добавить DEBUG_DEBUG при приёме keepalive-пакета 2026-06-01 10:03:14 +03:00
Evgeny 803387f2b4 router close_all_for_node: integrate with BGP remove_conn, fail fast on no route, stop PAUSE cycle
- route_bgp_remove_conn: call etcp_router_conn_close_all_for_node() when node loses all paths
- etcp_route_send: check BGP route before creating conn (return -1 early, no conn leak)
- remote_proxy: check rp_send_msg return value, close conn on error to break PAUSE↔FLUSH cycle
2026-06-01 01:44:29 +03:00
Evgeny 53d3f26e87 etcp_router: CLOSE/RST signaling flags in seq, close notification to services
- ROUTER_SEQ_CLOSE_FLAG (0x80000000): normal conn close
- ROUTER_SEQ_RST_FLAG   (0x40000000): conn not found
- router_send_to(): SVC_ROUTE with flags, no conn needed
- router_send_close_to_service(): notify local service [svc_id],len=1
- router_close_and_notify(): CLOSE to remote + close to local + cleanup
- etcp_router_conn_close(): sends CLOSE to remote before cleanup
- etcp_router_conn_close_all_for_node(): close all conns for dead peer
- recv_cb: flags checked only when pl_len==0 (avoid seq collision)
- etcp_router_destroy: close notification to each service
2026-06-01 01:12:52 +03:00
Evgeny 21cfe2d381 etcp_router: remove legacy, auto-conn for all services, send_q inflight control, remove tcp_proxy internal seq
- All SVC_ROUTE traffic now auto-creates ETCP_ROUTER_CONN, no legacy path
- etcp_route_send: auto-conn + seq + inflight check → send_q if full
- Inflight control via send_q: queue instead of drop, drain on ACK, retry timer 50ms
- router_send_one(): unified SVC_ROUTE wrapper with seq assignment
- router_deliver/try_assembly: pass real ETCP_CONN* to handlers (NAT needs it)
- tcp_proxy: remove internal 16-bit seq (send_seq/recv_last_seq/recv_init), shrink HDR 8→6
- remote_proxy: same seq removal, HDR/constants adjusted
- conn init/close/destroy: manage send_q, send_resume_timer lifecycle
- test_etcp_router_unit: −legacy, +send_q inflight test (18 tests)
2026-05-31 22:55:23 +03:00
Evgeny ac3cdd7739 etcp_router: simplified TCP — reorder recv_q, dedup, ACK timers, inflight control, per-(remote,svc) conn state
- New SVC_ROUTE_HDR (packed struct, 22 bytes): cmd+dst+src+seq+svc_id
- ETCP_ROUTER_CONN: state per (remote_node_id, svc_id), hash-indexed in router_conns
- Reorder via recv_q (hash by seq), dedup with 32-bit circular compare
- Periodic ACK (100ms), idle ACK (500ms), inflight limit via tx_seq - rx_acked
- Legacy mode: no conn → direct delivery without reorder
- etcp_router_conn_get/send/close API for seq-managed connections
- Unit test test_etcp_router_unit: 18 tests, 3ms, no ETCP/sockets/BGP
2026-05-31 21:59:46 +03:00
Evgeny 0f0857c22a add non-blocking send with EAGAIN+retry timer, normalizer flow control on recv, remove client-side send_buf buffering 2026-05-31 18:09:50 +03:00
Evgeny c4e80e6fd7 add traffic debug: PROXY RECV/SEND/FEED for full DATA path tracing on client 2026-05-31 17:21:47 +03:00
Evgeny 9d4c5b00f4 fix: stop forwarding after CLOSE, send CLOSE for non-existent conn in proxy data handlers 2026-05-31 16:54:36 +03:00
Evgeny f691c01b45 перевод комментариев tcp/udp/icmp proxy на русский 2026-05-31 16:32:10 +03:00
Evgeny 9a5d8797af tun: socket pair in test_mode, remove ip_fd_mode from tcp_proxy, fix test_remote_proxy stream_id size 2026-05-31 15:58:30 +03:00
Evgeny d1c9ef8377 ag 2026-05-31 13:44:55 +03:00
Evgeny e5d11ef9c1 refactor tcp_proxy: remove local socket mode, simplify architecture
- remove sock_transport, transport abstraction, active connections API
- simplify proxy_conn to 13 fields (was 15), remove tcp_proxy_mapping linked list
- new protocol: stream_id 4B, seq 2B cyclic, HDR_SIZE 8B
- add ERROR subcmd for immediate abort, CLOSE for graceful half-close
- proper half-close: shutdown SHUT_WR, keep reading, no timer
- exhaustive DEBUG_ERROR/WARN on all error/unusual branches
- config parser: report unknown options with filename:line and valid keys
2026-05-31 13:35:26 +03:00
Evgeny c3eccd7b35 add backpressure support: uip_retry_timer, feed drain at closing_tun/sent, send buffering with retry
- proxy_uip_retry_cb: retry send from uip_to_transport every 5ms
- proxy_recv_cb: buffer to uip_to_transport when send fails, set retry timer
- proxy_feed: drain transport_to_uip when closing_tun || closing_sent (prevents RST)
- proxy_recv_cb(p==NULL): only tcp_recved + closing_tun, no drain
- proxy_poll_cb/tcp_proxy_destroy: cancel retry timer on cleanup
- uip_retry_timer field in proxy_conn
2026-05-31 03:31:03 +03:00
Evgeny 616986457f add DEBUG_ERROR in all silent proxy error paths: NULL checks, alloc fails, transport send fail, data drop 2026-05-31 01:58:44 +03:00
Evgeny 10b9c93fcb add SOCK:EVENT unified diagnostics on exit: NEW/CONN/SEND/RECV/CLOSE/TIMER with fd, stream, total count 2026-05-31 01:31:59 +03:00
Evgeny be791ee7b2 drain transport_to_uip at FIN to prevent data delivery after close (eliminates RST storm) 2026-05-31 01:05:12 +03:00
Evgeny 01254406d8 fix tun_platform_write return value on FreeBSD: subtract 4-byte AF_INET header from writev result 2026-05-31 00:53:19 +03:00
Evgeny c4b1436919 add TUN write hex dump diagnostic (ip_total, ver_ihl, proto, first 32 bytes hex) in traffic category 2026-05-31 00:44:17 +03:00
Evgeny f708b1d095 add TUN write diagnostic: log tun_platform_write errors 2026-05-31 00:31:47 +03:00
Evgeny deaeae747a remove feed block after CLOSE: feed must remain active for streaming and half_close after FIN 2026-05-31 00:14:42 +03:00
Evgeny 4b7a1656fc final RST fix: tcp_recved before closing_tun, feed stop after CLOSE, free unsent before tcp_close, proxy_try_close helper
- proxy_recv_cb(p==NULL): tcp_recved(pcb,gap) before closing_tun
- proxy_feed_from_transport: stop when closing_sent
- proxy_poll_cb cleanup: free unsent before tcp_close
- proxy_try_close helper: shared between poll_cb (500ms) and recv_cb
- pending_out check: don't close if data still queued for exit
- Configs: traffic=info, tun=info for diagnostics
2026-05-31 00:02:01 +03:00
Evgeny 40d35ee2a5 fix RST storm: stop proxy_feed after CLOSE sent + free unsent before tcp_close 2026-05-30 23:38:42 +03:00
Evgeny 80e4e4fd5d fix RST on FIN: tcp_recved in proxy_recv_cb BEFORE closing_tun, guard proxy_err_cb with !closing_sent 2026-05-30 23:24:37 +03:00
Evgeny 8de8b5bb41 fix RST storm: tcp_recved before tcp_close in proxy_poll_cb cleanup 2026-05-30 23:06:28 +03:00
Evgeny 7c668b5d6c add lwIP state diagnostics: tcp_alloc cwnd/sndbuf, proxy_poll_cb cwnd/unsent/unacked, active_pcbs counter 2026-05-30 22:40:31 +03:00
Evgeny ba03cab182 fix tcp_proxy connection lifecycle: closing_sent, destroy transport, shutdown+timer on exit
- Add closing_sent flag to proxy_conn (CLOSE sent to exit, no need to wait for reply)
- proxy_poll_cb: send CLOSE when done || sndbuf==0; cleanup when tun && (rem||sent)
- Destroy transport in proxy_poll_cb cleanup (fix 56-byte leak)
- remote_proxy_handle_close: shutdown(SHUT_WR) + 2s timer instead of immediate rp_conn_free
- Remove SHUTDOWN subcommand entirely (not needed with new lifecycle)
2026-05-30 22:03:37 +03:00
Evgeny ca3215ab91 fix half_close: send CLOSE from proxy_poll_cb when no pending data, shutdown not close on exit 2026-05-30 20:52:25 +03:00
Evgeny f9d5495993 fix tcp_proxy deadlock: send CLOSE on TUN FIN + full proxy diagnostics
- proxy_recv_cb(p=NULL): now calls transport->close() to signal exit side
- Add PROXY FIN/CLOSE/CONNECTED/DATA/FEED/TCP_IN/TCP_OUT logs
- Add RP RECV/SEND/CLOSE logs on exit side
- Fix queue_dgram_free/queue_entry_free ordering (UAF) in all proxy files
- Change TRAFFIC logs from ERROR to INFO level
- Add 2 extra 1MB tests in run_test.sh to verify no post-stress stall
- stress_client.py: add --verbose per-thread timing (connect/send/recv ms)
- mem.c: zero freed memory for early UAF detection
2026-05-30 20:42:44 +03:00
Evgeny 4e6d57e371 clear freed memory in u_free_impl to catch UAF earlier 2026-05-30 20:03:33 +03:00
Evgeny a02c474b94 fix use-after-free in queue_entry_free/dgram_free ordering + add tcp_proxy stress test + proxy diagnostics logging
- Fix queue_entry_free before queue_dgram_free in all proxy files (UAF bug causing segfault)
- Add PROXY/RP prefixed diagnostic logs for transparent proxy flow tracking
- Add tcp_proxy integration test: real utun instances, ETCP, remote_proxy, DNAT routing
- 7 test scenarios: basic_1mb, concurrent_5, concurrent_20, half_close, idle, conn_refused, stress (10 threads x 20 iterations)
- stress_client.py with --verbose per-thread phase timing (connect/send/recv)
2026-05-30 20:01:05 +03:00
Evgeny 2585ef6f29 set FreeBSD TUN remote address to 192.0.2.1 instead of same as local 2026-05-30 13:13:08 +03:00
Evgeny 6521451493 disable BACKTRACE_ENABLED to fix lld linker error with backtrace symbols 2026-05-30 02:17:46 +03:00
Evgeny 2144eea46f fix compiler warnings: C23 label+declaration, uint16_t overflow check 2026-05-30 02:12:47 +03:00
Evgeny 7c71ef683c add lwIP TCP stack test with fixes: rcv_wnd tracking, FIN compensation, tmr_phase dual-counter, trace ring buffer, stats counters 2026-05-30 01:39:34 +03:00
Evgeny 8994d7a834 update timeout_heap and u_async: O(1) cancel, heap cancel bubble-up fix, epoll callback safety, uasync_stop, socket_array u_realloc 2026-05-29 01:11:37 +03:00
Evgeny 2d516bf16e fix route_bgp etcp_send leaks and etcp_socket_remove infinite loop on link->conn==NULL 2026-05-14 03:52:13 +03:00
Evgeny 6c2aeb69a1 fix INIT bounds checks and eim_nat port uint16_t wrap 2026-05-14 03:50:01 +03:00
Evgeny d6c6ca9eb3 fix route_bgp/route_ping: old paths queue leak, etcp_send fail leak, nat_check_arg leak on cancel 2026-05-14 03:40:24 +03:00
Evgeny 5aa9bdbd64 fix pkt_normalizer: null deref on alloc fail, mtu underflow, unchecked pn_buf_renew in loop 2026-05-14 03:38:21 +03:00
Evgeny 194c8398c1 fix ETCP: dgram leak on inflight pool exhaustion, cancel dangling timers on link close/new 2026-05-14 03:35:26 +03:00
Evgeny 4d5afe5ae9 fix uasync: posted_tasks delivery on Linux, create cleanup leaks, atomic array expand 2026-05-14 03:32:23 +03:00
Evgeny 06b94d37e8 add audit findings document (51 issues across 12 modules) 2026-05-14 03:25:57 +03:00
Evgeny 1a4b0a9de6 fix stale pointer after realloc in uasync socket/event handlers 2026-05-14 01:24:06 +03:00
Evgeny f18037ece2 add 16-bit seq to TCP proxy DATA for loss/duplicate detection 2026-05-13 13:28:05 +03:00
Evgeny b829ff7001 fix use-after-free in tests: swap queue_entry_free/queue_dgram_free order, fix dangling timer handles and stack args; add ETCP MTU bounds, assembly overflow check, etcp_update_mtu 2026-05-13 12:38:20 +03:00
Evgeny 60cebb2535 fix TCP data stall: feed transport_to_uip on ETCP recv, tcp_poll interval 0→2 for close detection 2026-05-13 10:31:38 +03:00
Evgeny 3258a64df4 add periodic cleanup timers for ICMP pending requests and UDP flows 2026-05-13 09:02:24 +03:00
Evgeny cec736a4b0 redesign TCP close: closing_tun/closing_rem flags, poll-driven cleanup, proper half-close 2026-05-13 03:18:29 +03:00
Evgeny 238fb0eb23 fix remote_proxy: cleanup socket on EOF/error/connect-fail to prevent infinite callback loop 2026-05-13 02:32:26 +03:00
Evgeny f1bd65f450 fix lwIP TCP checksums on LE: remove htons from pseudo-header sum, add htons to return values 2026-05-13 02:11:59 +03:00
Evgeny f85f2f20fb fix tcp_pseudo_checksum: add ntohs() to correctly extract 16-bit IP halfwords from NBO on LE 2026-05-13 01:55:14 +03:00
Evgeny b1e50c2838 fix tcp_proxy_ensure_outbound_listen: pass network byte order port to tcp_bind 2026-05-13 01:37:59 +03:00
Evgeny 76e916f94d add transparent outbound TCP proxying: dynamic listen on unknown ports, fwd to exit 2026-05-13 00:47:40 +03:00
Evgeny 01cf7f67b6 fix TCP close: properly close lwIP pcb on both sides, fix use-after-free, add poll close path 2026-05-13 00:16:35 +03:00
Evgeny c396b91fde fix missing IP checksum in icmp/udp deliver_reply, fix udp output_queue->input_queue 2026-05-12 23:38:35 +03:00
Evgeny 889f6d5c3a fix icmp_proxy: write reply to input_queue (not output_queue) to reach TUN device 2026-05-12 23:19:56 +03:00
Evgeny 13d469d866 add orig_src_ip to ICMP proxy protocol, fix deliver_reply dst_ip (was wrong tun_ip_addr) 2026-05-12 23:03:49 +03:00
Evgeny 67d57c9751 add etcp_router INFO logs for ICMP_PROXY reply path (send+deliver) 2026-05-12 22:45:57 +03:00
Evgeny 02c7153822 add icmp_proxy diagnostic logging on all reply path stages 2026-05-12 22:30:16 +03:00
Evgeny 07540b7ff8 fix icmp: use SOCK_RAW for FreeBSD compat, add socket diagnostic logging 2026-05-12 22:08:30 +03:00
Evgeny d00996243d fix lwip_tcp_priv.h: use #ifndef _WIN32 for arpa/inet.h (FreeBSD compat) 2026-05-12 22:00:41 +03:00
Evgeny edeb83ce89 replace fake ICMP echo reply with test_loopback flag, honest error when no raw socket 2026-05-12 21:46:16 +03:00
Evgeny d3b6057f82 add framed read for raw fd (2-byte len prefix), TCP_NODELAY, fix test half-close ordering 2026-05-12 19:43:44 +03:00
Evgeny d0af315ff6 fix uasync set_timeout(0) ordering, dummynet shaper initial delay, add tests
- uasync_set_timeout(0): use FIFO immediate_queue instead of timeout_heap
  to guarantee execution AFTER all currently-expired heap timers
- uasync_cancel_timeout: search immediate_queue for timeout=0 timers
- dummynet: start initial shaper with bandwidth-based delay instead of 0
  (prevents queue from never filling up when packets arrive sequentially)
- test_dummynet: rename get_time_us -> now_us (conflict with u_async.h)
- test_u_async_comprehensive: fix immediate_timeouts, add timeout_ordering test
2026-05-12 13:42:16 +03:00
Evgeny ad8bc0ae00 fix lwip_tcp port byte order (double-htons), mapping lookup, socket error handling
- Fix port encoding: PCB stores ports in host byte order, tcp_create_segment uses htons
- Fix tcp_bind/tcp_connect: ntohs incoming network-order ports
- Fix demux comparisons: use host-order sport/dport statics instead of in-place ntohs
- Fix tcp_output_alloc_header_common: htons for port output
- Add static sport/dport variables (host byte order) to avoid tcphdr corruption
- Fix sock_transport_error_callback: handle EPOLLERR during non-blocking connect
- Fix mapping local_port: store host-order port for find_mapping_by_port
- Fix tcp_proxy_raw_read/tun_input: use IP total length for packet boundaries
- test_tcp_proxy: reduce size to 2KB, fix port, add debug_init, add echo server log
- test_tcp_proxy handshake and basic data flow now working (known: cwnd/poll throughput tuning needed)
2026-05-12 12:16:09 +03:00
Evgeny c05e1c9991 replace uIP with adapted lwIP TCP stack (lwip_tcp)
- Replace uIP (src/uip/) with adapted lwIP 2.1.x TCP module
- Remove original src/lwip/ (replaced by src/lwip_tcp/)
- New src/lwip_tcp/ module: lwip_pbuf, lwip_tcp core/in/out
- Full TCP state machine, sliding window, congestion control
- Fast retransmit/recovery, Van Jacobson RTT estimation
- Nagle algorithm (TF_NODELAY), delayed ACK
- OOSEQ reassembly, TIME-WAIT handling
- Memory: memory_pool for PCB, listen PCB, tcp_seg
- Timers: uasync periodic timer drives fast/slow timers
- Output: callback-based IP packet output (TUN/raw-fd)
- Error codes: LERR_ prefix to avoid conflicts
- pbuf: simplified PBUF_RAM with proper header space reservation
- Integrate into tcp_proxy.c/h with per-pcb callbacks
- Active connection API: struct proxy_conn* instead of int conn_idx
- Makefile: remove uip, add lwip_tcp files
- test_tcp_proxy updated for new API; test_tcp_proxy_remote disabled
- Known: test_tcp_proxy data transfer needs flow tuning (WIP)
2026-05-11 22:51:48 +03:00
Evgeny a6311a17d0 add UDP/ICMP proxy, refactor tcp_proxy via_node to instance-level
- UDP proxy: datagram relay client↔exit over ETCP
- ICMP proxy: echo ping relay client↔exit over ETCP
- Move via_node_id from per-forward-mapping to global tcp_proxy setting
- Support multiple concurrent tcp_proxy instances (no singleton g_tcp_proxy)
- Cap handshake padding to stay within 1472+overhead
- Handle non-TCP packets (UDP/ICMP) in tcp_proxy raw/tun input paths
- Add utun_instance_create_from_str() for test config creation
2026-05-11 18:37:36 +03:00
Evgeny 9b59415f83 add tcp_proxy and remote_proxy sections to sample config 2026-05-11 00:57:21 +03:00
Evgeny 17e95cc2a3 fix remaining pointer-sign warnings in sc_set_peer_public_key callers 2026-05-10 19:03:42 +03:00
Evgeny 68f4ae12c8 fix all compiler warnings: array-bool, pointer-sign, tautological, unsequenced 2026-05-10 18:56:06 +03:00
Evgeny a0e5446790 migrate NAT and DATA to etcp_router, fix warnings and destroy order 2026-05-10 14:00:49 +03:00
Evgeny 2611a05d81 revert unnecessary return -1 in void-test ASSERT macros; only test_bgp_route_exchange uses ASSERT in int main() 2026-05-10 00:49:46 +03:00
Evgeny fcb6a59ca9 fix ASSERT macros: return -1 instead of bare return in int main() (FreeBSD -Wreturn-mismatch) 2026-05-10 00:44:59 +03:00
Evgeny 075d97a459 fix .gitignore: stop ignoring test_*.c source files; add missing test_ipv6_sockets, test_tcp_proxy, test_radix, test_route6_lib sources 2026-05-10 00:34:28 +03:00
Evgeny c24a6367cc add IPv6 default route interface auto-detection for permanent/temporary IPv6 without netif
- get_default_route_netif_index(): UDP connect trick + getifaddrs to find default route iface
- etcp_socket_add(): auto-detect netif_index when not specified in config
- queue_new() API: explicit index_offset/index_size params instead of implicit from hash_size
- radix: cleanup types (uint8_t*), replace bzero/bcmp with memset/memcmp, remove kernel ifdefs
- route6_lib: new IPv6 routing library + get_node_v6_routes()
2026-05-10 00:05:23 +03:00
Charlie Root e1f051856f radix 2026-05-09 12:19:20 +03:00
Evgeny 2565e8d008 add uIP TCP/IP stack (minimal — only files needed for tcp_proxy build) 2026-04-29 00:20:09 +03:00
Evgeny 9869cf66f1 add etcp_router service routing layer + remote TCP proxy (uip+socket via router)
etcp_router: routable service packets with dst/src node_id
- etcp_router_bind/unbind: per-instance service handler registry
- etcp_route_send: send to node by id via BGP routing
- multi-hop forwarding through intermediate nodes
- loopback optimization (dst==self dispatches directly)

remote_proxy: exit node creating OS sockets on demand
- handles CONNECT/DATA/CLOSE via etcp_router
- non-blocking socket connect with uasync callbacks
- echo back response data to requesting node

tcp_proxy: etcp_transport for remote proxy
- via_node_id in mapping selects between sock/etcp transport
- unified handler for both tcp_proxy and remote_proxy roles
- config: forward = port -> ip:port via NODE_HEX [remote_proxy] section

tests: test_etcp_router (20 pkts bidirectional), test_remote_proxy (echo)
2026-04-28 20:01:12 +03:00
Evgeny 5e834b519f mass_update: log all sessions to files, terminal output only for errors 2026-04-28 00:00:40 +03:00
Evgeny 171e70a810 add inflight data return to normalizer on reinit + configurable inflight min/max limits + test 2026-04-27 23:50:52 +03:00
Evgeny dea3a39598 fix: reconnect test — cancel timers in conn_reset, add init_timer cancel on link_state=3, add reinit resilience to test
- etcp_conn_reset: cancel retrans_timer/ack_resp_timer via uasync_cancel_timeout
  before nulling (was leaking timer handles, confirmed by 'Timer leak: diff')
- etcp_connections: cancel link->init_timer explicitly when link_state becomes 3
  on both client and server INIT_RESPONSE paths (prevents spurious INIT after
  connection is established that could trigger cascading reinits)
- etcp_connections: remove duplicate conn->session_id assignment in new conn path
  (session_id is set later via conn_reinit flow)
- pkt_normalizer: resume output queue callback after pn_reset
- Add test_etcp_reconnect with reinit detection — monitors conn->reinit_count
  and restarts data transfer if reinit occurs during active phase
- Add etcp_dump.c/h — ETCP connection state dump utility for debugging
2026-04-27 19:46:45 +03:00
Evgeny 0f8df44b2b build: out-of-tree VPATH build, tests run once with colored timing
- Root Makefile: hand-written bootstrap delegating to build/
- Makefile.am: simplified to SUBDIRS + clean-local
- build.sh: delegates to make, autoreconf only
- tests: removed duplicate TESTS run, added colored [PASS]/[FAIL]/[SKIP] with timing
- .gitignore: added /build/ for out-of-tree artifacts

Result: 0 .o files in source dirs, 29/29 tests pass
2026-04-27 13:48:22 +03:00
Evgeny d0d4199273 etcp: unified send hook via etcp_udp_send, remove loss_rate, add dummynet_filter inline mode, rewrite BGP test
- Add etcp_udp_send_fn_t hook typedef, send_hook/send_hook_ctx fields to ETCP_LINK
- Implement etcp_udp_send() wrapper — all UDP sends go through one point
- Remove loss_rate from ETCP_SOCKET, CFG_SERVER, config parser, send path
- Add dummynet_filter: inline send-hook with loss/delay, no proxy socket needed
- Rewrite test_bgp_route_exchange: 3-node chain A(2 links)↔B↔C, 7 phases
  with dummynet_filter-based link failure/recovery simulation
- Clean up tests: remove skeleton test_routing_mesh, 6 debug/stale test files
- Refactor test_etcp_simple_traffic and test_pkt_normalizer_etcp (remove debug spam)
- Fix test_etcp_two_instances: remove private field access, fix false PASS on timeout
2026-04-27 12:43:52 +03:00
Evgeny e7c32d9ac1 1 2026-04-26 19:58:37 +03:00
Evgeny 7d03749522 1 2026-04-26 19:53:05 +03:00
Evgeny 61fee27478 congestion: burst-based BW measurement + adaptive inflight (slow start/cong avoidance/loss response)
New features:
- get_time_us() in lib/u_async.c/h for µs-precision timing
- Burst measurement (ETCP_SECTION_MEAS_TS 0x07, MEAS_RESP 0x08, FILLER 0x09)
  - 12 MTU packets sent at line rate, receiver measures inter-packet gaps
  - Burst triggered when link saturated + min 500ms interval
  - Result: measured BW -> bandwidth for shaper, burst_target_bdp for window cap
- 3-phase adaptive inflight in link_stats_timer_cb:
  - Slow start: windows doubles each RTT, exits on loss/RTT spike/threshold
  - Congestion avoidance: additive increase, proportional RTT shrink, multiplicative loss shrink
  - Burst target cap: inflight_lim <= burst_target_bdp * 1.1
- Dynamic optimal_inflight = sum(link->inflight_lim_bytes), recalculated on change
- Adaptive win_timebase = rtt/2 (was hardcoded 10ms)

Fixes:
- dummynet.c: shaper formula fixed (80000 -> 80)
- dummynet.c: queue_data_put call fixed (3 args -> 2)
- etcp.c: SIZE ERROR format string (missing log_name argument)
- etcp_request_pkt: FILLER leaves room for PAYLOAD overhead

Tests:
- test_etcp_congestion.c: single-threaded, 5-phase BW/delay/loss test
  - sender + receiver + dummynet in one uasync
  - CSV metrics output every 100ms
- All 29 existing tests pass
2026-04-26 00:17:32 +03:00
Evgeny edfe665837 log: move individual ping logs from INFO/TRACE to DEBUG level 2026-04-25 21:59:34 +03:00
Evgeny 6c057ca89a fix BGP: delay up_cbk until connection initialized, fix send_table_request leak 2026-04-25 21:46:35 +03:00
Evgeny a356eedc0d cleanup BGP/connection logging
- Consolidate on_up/on_down to single INFO line with peer_id
- Remove DEBUG_CATEGORY_DEBUG duplications from route_bgp.c
- NODEINFO: inline lightweight dump (no malloc), verbose log -> DEBUG
- NAT_INFO: remove hex dump duplications, single clean INFO line
- Send NODEINFO per-recipient: INFO->DEBUG_DEBUG
- Per-ping callbacks: INFO->DEBUG_TRACE
- Per-packet traffic direction: INFO->DEBUG_TRACE
- route_insert/delete: use node_id instead of pointer
- Add cancel_all log, add_to_senders conn name
- send_full_table: ERROR->WARN for consistency checks
- is_local_subnet: DEBUG->TRACE
- Fix etcp_send_ping BGP category
2026-04-25 20:06:48 +03:00
Evgeny 227a1aafad cleanup debug logging 2026-04-25 19:43:51 +03:00
Evgeny 048d1c18b1 fix: restore lost if(changed) block in update_my_nodeinfo + add debug warnings 2026-04-25 19:41:17 +03:00
Evgeny bbebc74bdb fix etcpmon keyboard: move Tab/Enter/Esc from WndProc to message loop pre-translate 2026-04-25 19:27:01 +03:00
Evgeny 973c00e544 add etcpmon keyboard navigation: Tab between Server/Port, Enter=Connect, Esc=Disconnect 2026-04-25 19:22:00 +03:00
Evgeny fb76b4252f fix etcpmon: stray brace in nat_type_to_string, add pipefail to build scripts 2026-04-25 19:14:28 +03:00
Evgeny 456d274e12 fix mass_update: remove git remote http->https rewrite (port 443 blocked on remotes) 2026-04-25 19:08:08 +03:00
Evgeny c391d497b4 fix mass_update: skip tests build, fix git http redirect, speed up deploy 2026-04-25 19:06:18 +03:00
Evgeny 4e040b2891 fix mass_update: add pipefail so make errors stop the update script 2026-04-25 18:54:07 +03:00
Evgeny f6ef38aa5d update documentation 2026-04-25 18:42:20 +03:00
Evgeny 9c509d4c86 add connectivity probing + NAT type rename OPEN->EIM, RESTRICTED->STRICT
- Rename NAT types: OPEN->EIM, RESTRICTED->STRICT across all code
- Add flat addr list {type,ip,port,socket_id} to NODEINFO (NODEINFO_IPV4_ADDR)
- Add NODEINFO_IPV4_SOCKET_META for per-socket metadata
- Add NODE_CONNECTIVITY with per-type probe status and min_rtt
- New module: route_connectivity.c/h (probing engine with socket fallback)
- STRICT verified addresses excluded from NODEINFO broadcast
- Trigger probing on new/updated BGP node, cancel on remove/withdraw
- Update NAT type names in etcpmon GUI
2026-04-25 18:41:45 +03:00
Evgeny 39544e6b2b Fix: Add bounds/mtu checks, underflow protection and null deref fix for ETCP robustness 2026-04-25 12:38:05 +03:00
Evgeny 2fadec947b Fix: Split multi-var init and rename s_net/d_net to src_net/dst_net to avoid MSYS2 S_un macro conflict 2026-04-25 01:21:38 +03:00
Evgeny 595d2b177b Refactor: Replace custom tc_hton*/tc_ntoh* with standard htobe16/be16toh/htobe32/be32toh, move htobe16/be16toh to platform_compat.h 2026-04-25 01:16:09 +03:00
Evgeny 69b49d424b Fix: Include platform_compat.h before winsock2.h in debug_config.h so tc_htonl/tc_htons macros are defined first 2026-04-25 01:02:50 +03:00
Evgeny 6284a95267 Fix: Define tc_htonl/tc_htons as macros before winsock2.h to avoid struct-in_addr redefinition 2026-04-25 00:59:50 +03:00
Evgeny 8cb05da11c Fix: Add forward declaration of struct UTUN_INSTANCE in route_node.h 2026-04-25 00:54:20 +03:00
Evgeny aafa0cfcab Fix: Add portable tc_htonl/tc_htons/tc_ntohl/tc_ntohs macros, replace winsock2-incompatible hton*/nto* in NAT test files 2026-04-25 00:52:33 +03:00
Evgeny 3dd8fa4085 Fix: Remove arpa/inet.h from test_nat_stress.c and test_nat_transport.c, use platform_compat.h for byte-order funcs 2026-04-25 00:50:46 +03:00
Evgeny ba30f2adc6 Fix: Remove arpa/inet.h from test_nat_engine.c, use platform_compat.h for byte-order funcs 2026-04-25 00:48:40 +03:00
Evgeny 907ef244c6 NAT: Full Cone (EIM) NAT with ETCP_ID_NAT protocol, per-node routing, and comprehensive test suite
- New ETCP protocol ETCP_ID_NAT (0x02) for NAT traffic between nodes
- eim_nat.c/h: Pure NAT engine (table, port allocation, checksum updates, egress/ingress)
- nat_transport.c/h: Transport layer (NAT TUN, ETCP binding, client/provider roles)
- route_bgp_find_conn_for_node(): shared utility for optimal connection lookup
- tun_init_nat(): dedicated TUN creation for NAT
- Config: [nat] section with tun_ip, port_start/end, nat_via, forward rules
- Fix: csum_update_ip/transport use htonl() halves (correct on LE systems)
- Fix: IP_FRAG_MF_MASK changed to 0x2000 (after ntohs, MF is bit 13)
- Tests: 26 engine unit + 4 transport integration + 7 stress (29 total)
2026-04-25 00:43:25 +03:00
Evgeny b7da11c2bf Crypto: Refactored ETCP header size calculations with offsetof-based defines, increased CCM tag to 16 bytes 2026-04-24 16:03:07 +03:00
Evgeny a66edb8f5f Crypto: Added SHA256-based KDF for session key derivation from ECDH shared secret 2026-04-24 15:43:58 +03:00
Evgeny 11d511797a Fix compiler warnings: remove incorrect address-of operator and add getpid for Windows
- etcp_connections.c:1142: remove & before addr in sockaddr_storage_to_str call
- test_etcp_simple_traffic.c: add #include <process.h> and #define getpid _getpid for Windows
2026-04-24 14:48:29 +03:00
Evgeny 45a181370f Add htobe32/be32toh macros for Windows build 2026-04-24 14:44:24 +03:00
Evgeny 27df3355f1 Fix getmyip.h Windows build: use winsock2.h instead of sys/socket.h 2026-04-24 14:39:12 +03:00
Evgeny e0ce9fefc3 Fix allowed_keys check to only apply to ETCP_INIT packets, not PING/PONG
The allowed_keys authorization was incorrectly checking all INIT-decrypted
packets including PING/PONG. Moved the check after packet type validation
so it only applies to ETCP_INIT_REQUEST/NOINIT (incoming connection requests).

Fixes test_etcp_ping and test_nat_detection failures.
2026-04-24 14:37:08 +03:00
Evgeny 6aedf5fe91 Replace buggy get_default_route_ip() with get_outgoing_local_ip() for reliable local IP detection
- Add lib/getmyip.c/h with get_outgoing_local_ip() using UDP connect+getsockname
- Update etcp_socket_add() to use new function for 0.0.0.0 and :: bindings
- Support both IPv4 (8.8.8.8) and IPv6 (2001:4860:4860::8888) auto-detection
- Remove broken get_default_route_ip() from platform_compat (it returned gateway IP, not local IP)
- Keep get_interface_ip_by_index() for explicit interface bindings
- Update Makefile.am to include new getmyip module
2026-04-24 14:07:41 +03:00
Evgeny 724a9ff5c7 Add getmyip module for reliable local IP detection
- New lib/getmyip.c/h with get_outgoing_ip(), get_default_local_ip(), get_local_ip_by_index()
- Uses UDP connect + getsockname instead of /proc/net/route parsing
- Supports both IPv4 and IPv6
- Updated etcp_socket_add() to use new functions for interface_addr detection
2026-04-24 13:58:24 +03:00
Evgeny 697aee0351 Socket info: show config and netif IP in log, monitor shows interface IP 2026-04-24 01:23:52 +03:00
jeka 30413ebce0 1 2026-04-24 00:43:59 +03:00
Evgeny 20f3ba092f Config: Added [allowed_keys] section to sample config with documentation 2026-04-24 00:21:00 +03:00
Evgeny e39979bfac Config: Added [allowed_keys] section for client public key authorization on server
- Added CFG_ALLOWED_KEY struct with binary key storage (64 bytes) for fast memcmp comparison
- Implemented parsing of [allowed_keys] section with allow_all flag and per-key entries
- Added key validation in etcp_connections.c server handler (errorcode=8 for rejected keys)
- Default behavior: deny all if [allowed_keys] section is missing or allow_all=0 with no keys
- Added proper memory cleanup and debug logging for allowed_keys configuration
- Updated all test configs to include [allowed_keys] allow_all=1 for backward compatibility
2026-04-24 00:16:30 +03:00
Evgeny 3625a4e5d5 Restart NAT check when client address changes in INIT_REQUEST 2026-04-23 23:56:49 +03:00
Evgeny a3063ea47a NODEINFO: Add nat_addr/nat_port fields, use interface_addr for addr/port 2026-04-23 23:21:12 +03:00
Evgeny f02603f8ac Add socket type (public/nat/private) to handshake and skip private nodes in NAT detection 2026-04-23 22:44:43 +03:00
Evgeny bd749c1484 GUI: Widen sockets list 1.5x, shrink action field, move Send below 2026-04-23 22:11:02 +03:00
Evgeny 0c748501c8 GUI: Add NAT address to socket display in etcpmon 2026-04-23 22:02:42 +03:00
Evgeny 72c6b6f197 Reset NAT check status on etcp_conn_reinit when session_id changes
When peer's random session_id changes, etcp_conn_reinit() is called.
Previously NAT check status was not reset, so route_bgp_new_conn()
skipped NAT re-detection because nat_check_status was still
NAT_CHECK_IN_PROGRESS from the previous session.

Now etcp_conn_reinit():
- Cancels pending NAT-ping requests via route_ping_cancel_for_conn()
- Resets nat_type and nat_check_status to UNKNOWN/NONE for all links

This ensures NAT type is re-detected for all links after reinit.
2026-04-23 20:00:09 +03:00
Evgeny 4b9bffffbd Debug: Add diagnostic logging for PONG handling to trace ping response issues 2026-04-23 13:18:59 +03:00
Charlie Root d3d95c3c19 1 2026-04-22 23:15:41 +00:00
Evgeny bcb1c37c87 Fix two more UAF in route_bgp: move DEBUG_INFO before etcp_send
- route_bgp_send_nat_info: moved ip_to_str(pkt->nat_ip) log before etcp_send
- route_bgp_send_nat_check_req: moved ip_to_str(pkt->interface_ip) log before etcp_send

Both were reading from pkt memory that could be freed by etcp_send callback.
2026-04-23 02:09:09 +03:00
Evgeny 3959ed8afe Fix use-after-free: read request_id before etcp_send frees req_pkt
req_pkt memory could be freed by etcp_send callback before pending->request_id
was assigned. Store request_id in local variable and use it for pending.
2026-04-23 01:59:41 +03:00
Evgeny 5b6455ccff Add debug logging for request_id tracking and pending list dump 2026-04-23 01:48:16 +03:00
Evgeny ae1c59f3a9 Fix unaligned uint64_t access in packed struct causing garbage request_id
- Changed request_id from uint64_t to uint32_t in BGP_PING_REQUEST/RESPONSE
- Added uint16_t reserved field after subcmd for proper alignment
- Updated next_ping_req_id from uint64_t to uint32_t in ROUTE_BGP
- Fixed all log format strings from %016llx to %08x
- All 26 tests passing
2026-04-23 01:38:17 +03:00
Evgeny 4760baa973 1 2026-04-23 01:15:15 +03:00
Evgeny 33fd5bc123 ETCP: add session_id to prevent false reinit on new link addition
- Added 32-bit session_id to ETCP_CONN struct (random per client)
- Session_id transmitted in INIT_REQUEST/RESPONSE after node_id
- Server: skip reinit if session_id matches existing connection
- Server: send INIT_RESPONSE (0x03) only when reinit actually done
- Server: send INIT_RESPONSE_NOINIT (0x05) for same session
- Client: ignore INIT_RESPONSE with wrong session_id
- Fixed tx_state initialization in etcp_conn_ready for noinit case
- All 26 tests passing
2026-04-22 23:53:25 +03:00
Evgeny 04f02ffead NAT: полное решение для отмены pending при падении conn
1. Добавлен via_conn в struct route_ping_pending - отслеживает через
   какое соединение (third_conn) отправлен запрос на ping
2. route_ping_cancel_for_conn теперь отменяет pending при падении ЛЮБОГО
   из двух conn: владельца линка (link->etcp) ИЛИ third_conn (via_conn)
3. nat_link_check_cb проверяет link->nat_check_status == IN_PROGRESS
   перед обработкой - защита от race condition когда conn упал раньше
   чем пришел ответ
4. Убраны избыточные DEBUG-логи, оставлены только информативные
2026-04-22 23:08:55 +03:00
Evgeny 07db51382e NAT: исправлена ошибка в route_ping_cancel_for_conn + добавлен дебаг
- Исправлена критическая ошибка: использовалось na->link->conn вместо
  na->link->etcp (struct ETCP_LINK.conn = ETCP_SOCKET*, а .etcp = ETCP_CONN*)
- Добавлен подробный дебаг в 4 места:
  * route_ping_send_req_addr - при создании pending
  * route_ping_cancel_for_conn - при поиске и отмене
  * route_bgp_remove_conn - состояние линков перед отменой
  * etcp_connection_close - очистка линков при закрытии
2026-04-22 22:58:36 +03:00
Evgeny ca42e2fa66 NAT: отмена pending NAT checks при удалении соединения
- route_bgp_remove_conn теперь отменяет pending NAT checks перед удалением
- Добавлена route_ping_cancel_for_conn: ищет pending запросы для conn,
  отменяет таймеры, сбрасывает arg->link = NULL, удаляет из списка
- struct nat_check_arg вынесена в route_bgp.h для видимости из route_ping.c
- После отмены pending, статусы линков с NAT_CHECK_IN_PROGRESS сбрасываются в NONE
2026-04-22 22:16:06 +03:00
Evgeny 7a4e7ca4ae Backup before NAT cancel fix 2026-04-22 22:07:33 +03:00
Evgeny 8e9db5211a 1 2026-04-22 21:16:46 +03:00
Evgeny 57dba26e7c GUI: поле действия и кнопка Send для отправки команд в utun
- Добавлен ETCPMON_CMD_ACTION в протокол (32 байта текст)
- В GUI добавлено поле ввода и кнопка Send правее блока Sockets
- Добавлен etcpmon_client_send_action для отправки команд
- Обработка action 'nat' в control_server: запускает NAT check для всех линков
- Сделана route_bgp_start_link_nat_check не-static
- Добавлена route_bgp_request_nat_check_all
- Исправлен баг route_ping.c (request_id -> nonce)
- Исправлена линковка tests/Makefile.am
2026-04-22 20:52:30 +03:00
Evgeny 5b0a743e02 1 2026-04-22 20:35:05 +03:00
Evgeny b0faf7d372 mass_update: по умолчанию запускает fast без аргументов 2026-04-22 18:29:31 +03:00
Evgeny 0984ea749a Исправлен byte order для IP адресов в NAT detection flow
- etcp_connections.c: парсинг NAT IP через memcpy (сохраняет NBO)
- route_bgp.c: убраны ntohl() для IP в NAT detection и route_bgp_send_nat_info
- route_ping.c: убран htonl() для IP в ping request
- тесты: обновлены ожидания NBO для nat_ip
2026-04-22 18:23:32 +03:00
Evgeny b466d14fe4 Добавлено поле interface_addr в ETCP_SOCKET и interface_ip/port в BGP_NAT_CHECK_REQ
- platform_compat: get_interface_ip_by_index() для получения IP по ifindex
- ETCP_SOCKET: interface_addr заполняется из конфига/interface/default route
- BGP_NAT_CHECK_REQ: добавлены interface_ip/interface_port (network byte order)
- route_bgp_send_nat_check_req: отправляет interface_addr линка
- route_bgp_handle_nat_check_req: принимает interface данные в sockaddr-заглушку
2026-04-22 18:16:49 +03:00
Evgeny 2e3eb0976a Add RX/TX dup counters to ETCP monitor GUI
- Add IDC_EDIT_ETCP_RX_DUP and IDC_EDIT_ETCP_TX_DUP controls
- Insert RX Dup / TX Dup fields after Retrans in ETCP Metrics group
- Update tooltips, metrics display and clear functions
2026-04-22 16:28:33 +03:00
Evgeny 385aa066b2 Add duplicate packet counter to ACK frames
- Add rx_dup_count (local) and tx_dup_count (remote via ACK) to ETCP_CONN
- Extend ACK section format: add 16-bit rx_dup_count after last_delivered_id
- Reconstruct 32-bit tx_dup_count from received 16-bit diff
- Update etcpmon protocol and control_server to expose counters
- Update packet dumper for new ACK format
2026-04-22 16:04:11 +03:00
Evgeny 9be03ece76 Update: mass_update.py - config in script dir, per-server SSH keys 2026-04-22 14:37:10 +03:00
Evgeny 7a46817799 Fix: read PID from pidfile instead of using shell $! 2026-04-22 13:16:26 +03:00
Evgeny ceef0d701a Suppress route command output to console 2026-04-22 12:42:52 +03:00
Evgeny c72d2afbef 1 2026-04-22 12:37:02 +03:00
Evgeny a44d9c10a1 1 2026-04-22 12:35:51 +03:00
Evgeny 1c501f9690 Add remote update scripts for mass deployment
- utun_update.sh: Shell script for updating single instance
  * fast mode: git pull + make clean + make
  * full mode: complete rebuild with autotools
  * Auto-detects project root from script location
  * Handles PID-based process stop/start
  * Supports Linux and BSD (nproc/sysctl)
  * Returns proper exit codes (0-4)

- mass_update.py: Python script for parallel SSH updates
  * Parallel execution via ThreadPoolExecutor
  * Configurable via JSON (~/.utun_servers.json)
  * Supports custom port/user per server
  * Timeout and error handling
  * Summary output per server

Tools for remote mass deployment of uTun updates via SSH.
2026-04-22 12:24:48 +03:00
Evgeny e5e520de21 Update .gitignore to prevent accidental binary commits 2026-04-22 11:47:34 +03:00
Evgeny cbde38d52a Remove accidentally committed binaries and temp files 2026-04-22 11:45:54 +03:00
Evgeny 5d95f6db30 Merge branch 'master' of http://git.syqiq.tech/jeka/utun2 2026-04-22 11:40:24 +03:00
Evgeny c136db5666 Fix: round-robin link selection + byte order fixes + local_node update 2026-04-22 11:39:50 +03:00
Evgeny 77fd7c19de WIP: debug byte order diagnostics 2026-04-22 02:14:47 +03:00
jeka f02e1dccec 1 2026-04-22 00:46:00 +03:00
Evgeny 6a971389b6 channel select: round-robin and inflight 2026-04-22 00:04:20 +03:00
Evgeny 16a2aa959d etcpmon: translate tooltips to English 2026-04-21 20:47:09 +03:00
Evgeny 6127409c04 etcpmon: add tooltips for all metrics fields with variable names and descriptions 2026-04-21 20:21:08 +03:00
Evgeny dc0d700d5c Fix: free pkt_pool memory for keepalive packets (data_len=0) 2026-04-21 19:27:17 +03:00
Charlie Root 60e845ca94 1 2026-04-21 16:14:03 +00:00
Evgeny d74ab1c1c6 Add name parameter to uasync_set_timeout for timer identification in dumps 2026-04-21 19:06:45 +03:00
Evgeny 3c0b5f501a Fix: use allocated_count from mem.c for busy memory calculation
busy = u_get_allocated_count() - memory_pool_get_total_free_blocks()
2026-04-21 17:41:45 +03:00
Evgeny cee9a04348 GUI: add active timeouts and busy memory blocks counters
- Add timeout_heap_get_size() to get active timer count
- Add global counters in memory_pool: g_total_free and g_total_allocated
- Simplify busy memory calculation to single formula:
  busy = memory_pool_get_total_allocated() - memory_pool_get_total_free_blocks()
- Add new IDC_EDIT_ACTIVE_TIMEOUTS and IDC_EDIT_BUSY_MEMORY to GUI
2026-04-21 17:22:58 +03:00
Charlie Root e596564abd 1 2026-04-21 17:13:11 +03:00
Evgeny cfd1e53ee9 mem: add allocations and frees counters with u_mem_get_counters() 2026-04-21 15:35:46 +03:00
Charlie Root 4e2b1c0c01 1 2026-04-21 15:33:46 +03:00
jeka c57f94f38d 1 2026-04-21 15:18:09 +03:00
Charlie Root 09248bdbb6 1 2026-04-21 02:29:31 +03:00
Evgeny 41952703d9 Memory: use DEBUG for hex_dump output instead of printf 2026-04-21 02:27:33 +03:00
Evgeny f195d1d91e Memory: refactor hex_dump to use buffer with DEBUG output 2026-04-21 02:26:27 +03:00
Evgeny 7b305e820f 1 2026-04-21 02:20:53 +03:00
Charlie Root ea844734de 1 2026-04-21 01:56:48 +03:00
Evgeny 912eb8480c Memory: add hex dump for unfreed blocks in u_report_unfreed_blocks 2026-04-21 01:54:20 +03:00
Evgeny 2558942da1 Memory: add location tracking to pool alloc/free with debug logging 2026-04-20 22:48:27 +03:00
jeka b2ed0317a6 ping refactor 2026-04-18 22:26:54 +03:00
Evgeny 0157fe6035 1 2026-04-15 20:57:09 +03:00
Evgeny 53a97d2bab 1 2026-04-15 19:12:47 +03:00
jeka ae2b654a67 1 2026-04-15 19:10:18 +03:00
Evgeny 58eea06f60 1 2026-04-15 17:40:42 +03:00
Evgeny a1dd99c8d4 1 2026-04-15 17:17:00 +03:00
Evgeny d523c6ef45 1 2026-04-15 16:53:39 +03:00
Evgeny 6cba046409 1 2026-04-15 16:30:11 +03:00
Evgeny cac786f684 1 2026-04-15 16:21:15 +03:00
Evgeny 356e091b7a 1 2026-04-15 16:20:14 +03:00
Evgeny d278041330 1 2026-04-15 16:19:05 +03:00
Evgeny 70deb254b6 1 2026-04-15 16:17:41 +03:00
Evgeny fdd4ec02f6 1 2026-04-15 16:10:12 +03:00
Evgeny 9ed88d2418 1 2026-04-15 15:59:00 +03:00
jeka d975696543 1 2026-04-15 15:29:26 +03:00
Evgeny 8bad15b7bb 1 2026-04-15 15:15:15 +03:00
Evgeny 1a171d5a24 1 2026-04-15 12:32:40 +03:00
Evgeny 122e619649 1 2026-04-15 10:13:38 +03:00
jeka c36462ce09 Merge branch 'master' of http://git.syqiq.tech/jeka/utun2 2026-04-15 09:00:29 +03:00
jeka c1d2d26cae 1 2026-04-15 09:00:12 +03:00
Evgeny cd5d6f6e86 1 2026-04-15 00:26:27 +03:00
Evgeny 09fa26bc2c 1 2026-04-15 00:04:23 +03:00
jeka c0c366bd83 mon fix 2026-04-14 23:40:14 +03:00
Evgeny 0a44320245 1 2026-04-14 22:28:25 +03:00
Evgeny 27874ffa22 1 2026-04-14 22:14:35 +03:00
Evgeny c93b84529e 1 2026-04-14 21:55:47 +03:00
Evgeny ad35bbab15 add sockets to gui 2026-04-14 21:50:32 +03:00
Evgeny cd63f5a1b3 add sockets to gui 2026-04-14 21:33:10 +03:00
Evgeny e8e20cad88 add sockets to gui 2026-04-14 21:27:22 +03:00
Evgeny 6de88ec85f add sockets to gui 2026-04-14 21:24:39 +03:00
Evgeny 62a059abdb add sockets to gui 2026-04-14 21:17:46 +03:00
Evgeny 54982f2dfb ping 2026-04-14 19:19:41 +03:00
Evgeny 7cdb84c7a2 Add NAT detection with burst ping, test timing, fix port conflicts
- route_ping.c: burst mode - send next ping immediately on response
- route_ping.h: add target_ipv4/target_port/pubkey to BGP_PING_REQUEST
- test_nat_detection.c: add test for ping without embedded pubkey
- test_etcp_simple_traffic.c: use dynamic ports + fix timebase units
- Makefile.am: show test execution time in ms
2026-04-14 14:25:36 +03:00
Evgeny 31eb4a5d26 Add NAT detection with STUN-like ping and integration test 2026-04-14 12:02:43 +03:00
Evgeny b613e3a8e0 serializer 2026-04-14 02:03:59 +03:00
Evgeny 5dc8ad6784 1 2026-04-13 23:56:19 +03:00
Evgeny 2903b82941 Add test_serialize to Makefile and include serialize in libuasync.a
- tests/Makefile.am: add test_serialize to check_PROGRAMS
- lib/Makefile.am: add serialize.c/h to libuasync_a_SOURCES
2026-04-13 23:50:32 +03:00
Evgeny dba092d88b Add serialize library for binary serialization with dynamic allocation
- serialize.h/c: universal binary serialization for structures with
  variable-length fields (ASCIIZ, ARRAY) using pointers
- Auto allocation via u_malloc during deserialization
- Support for header (arbitrary bytes at buffer start)
- serialize_encode(): serialize structure to buffer
- serialize_decode(): deserialize from buffer (without header)
- serialize_free(): free structure and all its pointers
- tests/test_serialize.c: unit tests
2026-04-13 23:08:54 +03:00
Evgeny 3b31821316 Add test_route_ping.c test file 2026-04-13 20:42:47 +03:00
Evgeny 09195b2f61 Add IPv6 support for sockets and connections
- Add get_node_v6_sockets() function in route_node
- Add local_defaultroute_ip6 field to ETCP_SOCKET
- Fix IPv6 address logging in bind, init, new connection
- Fix socket lookup for IPv6 in init_connections
- Fix route_ping_handle_req to support both v4 and v6 sockets
2026-04-13 20:40:16 +03:00
Evgeny ffbc0da86f add ping 2026-04-13 20:12:13 +03:00
Evgeny 389124d327 Fix test_bgp_route_exchange: correct IP byte order constants and platform_compat.h endian include 2026-04-13 14:05:32 +03:00
Evgeny 05323ded60 WIP: backup before debugging test_bgp_route_exchange 2026-04-13 13:55:21 +03:00
Evgeny 07306e378b backup: state before full initialization for ping test with real links 2026-04-13 02:09:53 +03:00
Evgeny 20ff5a9ed1 backup: state before adding unit test for etcp_send_ping 2026-04-12 19:50:28 +03:00
Evgeny 8794c24893 backup: state before adding ping response for unknown addresses and etcp_send_ping with callback 2026-04-12 19:37:30 +03:00
Evgeny aaa2a13849 add smart reload logs/config 2026-04-09 21:00:32 +03:00
Evgeny 9c612486b3 add smart reload logs/config 2026-04-09 20:35:59 +03:00
Evgeny 85367fbe36 backup: before Variant B selective socket/client/link reload impl 2026-04-09 20:35:59 +03:00
jeka 0dbefab922 1 2026-04-09 14:39:07 +03:00
jeka cec2ffe2eb 1 2026-04-09 14:29:21 +03:00
jeka 04d3a0e52e add inflight to gui 2026-04-09 12:51:55 +03:00
Evgeny 3e2bf2be4a 1 2026-04-09 12:02:08 +03:00
Evgeny 29e54195ed 1 2026-04-09 01:33:35 +03:00
Evgeny 5a36b0d30d 1 2026-04-09 00:55:53 +03:00
Evgeny 766fd71296 1 2026-04-09 00:55:34 +03:00
Evgeny 8a27c7ad80 1 2026-04-09 00:52:49 +03:00
Evgeny 692b9054f7 1 2026-04-09 00:40:28 +03:00
Evgeny da09acd24f 1 2026-04-08 19:24:07 +03:00
Evgeny b4ccf548ed 1 2026-04-08 19:21:32 +03:00
Evgeny f094b9cabb 1 2026-04-08 19:05:45 +03:00
Evgeny 4ce48bbd7d 1 2026-04-08 15:35:05 +03:00
Evgeny f873e16a63 1 2026-04-08 02:19:36 +03:00
Evgeny 92673d744a 1 2026-04-08 02:13:44 +03:00
Evgeny 23622fc421 1 2026-04-07 18:54:52 +03:00
Evgeny 3904c9540d win_upd 2026-04-07 14:06:12 +03:00
Evgeny fb386976c5 win_upd 2026-04-07 14:03:40 +03:00
Charlie Root 6ddc69fec4 sliding_win 2026-04-07 10:42:13 +03:00
jeka ac09cd850f 1 2026-04-07 01:36:56 +03:00
jeka d6ce728965 1 2026-04-07 01:21:54 +03:00
jeka 1e193b8de0 1 2026-04-07 01:15:36 +03:00
jeka 52da3fbfe5 1 2026-04-07 01:01:18 +03:00
jeka 1f5e2d75db 1 2026-04-07 00:34:25 +03:00
jeka 1b0f393c6c 1 2026-04-07 00:30:26 +03:00
jeka 8b16559230 1 2026-04-07 00:17:50 +03:00
jeka e9ff5fcfdb 1 2026-04-06 21:35:46 +03:00
jeka b88dcf4a2c 1 2026-04-06 21:21:44 +03:00
jeka e82a95d529 1 2026-04-06 21:18:58 +03:00
jeka c3bfa7a8c9 1 2026-04-06 21:05:42 +03:00
jeka f1b6d90d71 Merge branch 'master' of http://git.syqiq.tech/jeka/utun2 2026-04-06 18:09:50 +03:00
jeka 7f0f28fd58 1 2026-04-06 18:09:40 +03:00
Evgeny 189a93c71d fix cpu consume at idle 2026-04-03 03:29:51 +03:00
Evgeny fd815150f6 Control: harden socket against malformed data and buffer overflows
- immediate close on bad size/type/payload
- recv_len guard to prevent overflow
- unknown commands now close without response
2026-04-02 20:27:08 +03:00
Evgeny c8bac5d322 1 2026-03-31 18:10:10 +03:00
Evgeny d733bcbf4d 1 2026-03-31 16:24:50 +03:00
Evgeny feb8024506 1 2026-03-31 13:37:49 +03:00
Evgeny c76646a39a 1 2026-03-31 13:15:51 +03:00
Evgeny 07e4f6f086 withdraw send fix 2026-03-31 03:51:15 +03:00
Evgeny 5050f9a8aa remove node after link down 2026-03-31 03:37:33 +03:00
Evgeny e47593cd77 1 2026-03-31 02:48:25 +03:00
Evgeny 5184181695 1 2026-03-30 23:38:56 +03:00
Evgeny b9f8930d1c 1 2026-03-30 23:13:53 +03:00
Evgeny 2d9fbd9911 1 2026-03-30 22:50:54 +03:00
Evgeny ceb94bee7a BGP: add table requesters to senders_list for broadcast propagation to further nodes 2026-03-30 22:49:06 +03:00
Evgeny 491f44e05e 1 2026-03-30 20:54:22 +03:00
Evgeny 2f97196ffd Fix: traces and null checks in etcp_send to debug/prevent core dump; better routing logs with dst 2026-03-30 20:31:32 +03:00
Evgeny 9a9a4ccf3c 1 2026-03-30 20:21:15 +03:00
Evgeny 2260e068f0 1 2026-03-30 20:16:56 +03:00
Evgeny d3c4b5f6e7 1 2026-03-30 20:07:04 +03:00
Evgeny 7ee3193dde 1 2026-03-30 19:41:20 +03:00
Evgeny 9628686d63 1 2026-03-30 19:28:34 +03:00
Evgeny a2e4a4b208 Merge: add local routes using hop_count==0 2026-03-30 19:00:13 +03:00
Evgeny 6938bfcb6a Routing: add local routes to table via hop_count==0 detection 2026-03-30 19:00:08 +03:00
173 changed files with 27627 additions and 4602 deletions

No files matched your search

+37 -2
View File
@@ -1,3 +1,6 @@
# Build directory (out-of-tree build)
/build/
# Build outputs
*.o
*.lo
@@ -6,10 +9,19 @@
*.so
*.so.*
utun
test_serialize
linux-x64-deb
# Temp files
*.c1
*.h2
1
# HTML files
index.html
# binary files
*.exe
**/*[^.]
# Autotools
Makefile
@@ -56,11 +68,20 @@ tests/*.log
tests/*.trs
tests/logs/
# Test binaries (files without extension in tests/)
# Mass update logs
logs/
# Test binaries (executables without extension), but track source/config files
tests/test_*
!tests/test_*.c
!tests/test_*.conf
!tests/test_*.json
tests/bench_*
!tests/bench_*.c
# All log files
*.log
utun.log*
# Local configs
*.cfg
@@ -68,8 +89,22 @@ tests/logs/
utun.conf
!utun.conf.sample
# IDE/Editor directories
.opencode/
# Tool binaries
tools/bping/bping
tools/proxy/udp_proxy
# Debug/temp artifacts
*.bak
*.debug
*.csv
__pycache__/
# MSVC temp files
cl
build_win.log
/wintun.dll
src/lwip_orig/
-102
View File
@@ -1,102 +0,0 @@
# Context7 Skill
## Purpose
Fetches **live, version-specific documentation** for external libraries and frameworks using the Context7 API. Ensures you always get current API patterns instead of potentially outdated training data.
**Golden Rule**: Always fetch live docs for external libraries—training data may be outdated.
## Quick Start
### Recommended: Use ExternalScout Subagent
The **ExternalScout** subagent is the recommended way to fetch external documentation. It handles:
- Library detection
- Query optimization
- Documentation filtering and sorting
- Formatted results with code examples
**Invocation**:
```
Use ExternalScout to fetch documentation for [Library Name]: [your specific question]
```
**Example**:
```
Use ExternalScout to fetch documentation for Drizzle ORM: How do I set up modular schemas with PostgreSQL?
```
### Alternative: Direct Skill Usage
You can also invoke the Context7 skill directly via bash:
```bash
# Step 1: Search for library
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY&query=TOPIC" | jq '.results[0]'
# Step 2: Fetch documentation
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=OPTIMIZED_QUERY&type=txt"
```
See `SKILL.md` for detailed API documentation.
## Supported Libraries
See `library-registry.md` for the complete list of supported libraries including:
- **Database & ORM**: Drizzle, Prisma
- **Authentication**: Better Auth, NextAuth.js, Clerk
- **Frontend**: Next.js, React, TanStack Query/Router/Start
- **Infrastructure**: Cloudflare Workers, AWS Lambda, Vercel
- **UI**: Shadcn/ui, Radix UI, Tailwind CSS
- **State**: Zustand, Jotai
- **Validation**: Zod, React Hook Form
- **Testing**: Vitest, Playwright
## Workflow
```
User Query
↓
ContextScout (searches internal context)
↓
No internal context found
↓
ContextScout recommends ExternalScout
↓
ExternalScout invoked
├─ Reads library-registry.md
├─ Detects library
├─ Loads query patterns
├─ Fetches from Context7 API
├─ Filters & sorts results
└─ Returns formatted documentation
↓
User receives current, actionable docs
```
## Files
- **`SKILL.md`** - Context7 API documentation and usage
- **`library-registry.md`** - Supported libraries, aliases, and query patterns
- **`README.md`** - This file (overview and quick start)
## Adding New Libraries
To add a new library to the registry:
1. Edit `library-registry.md`
2. Add entry under appropriate category:
```markdown
#### Library Name
- **Aliases**: `alias1`, `alias2`, `package-name`
- **Docs**: https://example.com/docs
- **Context7**: `use context7 for library-name`
- **Common topics**: topic1, topic2, topic3
```
3. (Optional) Add query optimization patterns
4. ExternalScout will automatically detect the new library
## Related
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
-85
View File
@@ -1,85 +0,0 @@
---
name: context7
description: Retrieve up-to-date documentation for software libraries, frameworks, and components via the Context7 API. This skill should be used when looking up documentation for any programming library or framework, finding code examples for specific APIs or features, verifying correct usage of library functions, or obtaining current information about library APIs that may have changed since training.
---
# Context7
## Overview
This skill enables retrieval of current documentation for software libraries and components by querying the Context7 API via curl. Use it instead of relying on potentially outdated training data.
## Workflow
### Step 1: Search for the Library
To find the Context7 library ID, query the search endpoint:
```bash
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY_NAME&query=TOPIC" | jq '.results[0]'
```
**Parameters:**
- `libraryName` (required): The library name to search for (e.g., "react", "nextjs", "fastapi", "axios")
- `query` (required): A description of the topic for relevance ranking
**Response fields:**
- `id`: Library identifier for the context endpoint (e.g., `/websites/react_dev_reference`)
- `title`: Human-readable library name
- `description`: Brief description of the library
- `totalSnippets`: Number of documentation snippets available
### Step 2: Fetch Documentation
To retrieve documentation, use the library ID from step 1:
```bash
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=TOPIC&type=txt"
```
**Parameters:**
- `libraryId` (required): The library ID from search results
- `query` (required): The specific topic to retrieve documentation for
- `type` (optional): Response format - `json` (default) or `txt` (plain text, more readable)
## Examples
### React hooks documentation
```bash
# Find React library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=react&query=hooks" | jq '.results[0].id'
# Returns: "/websites/react_dev_reference"
# Fetch useState documentation
curl -s "https://context7.com/api/v2/context?libraryId=/websites/react_dev_reference&query=useState&type=txt"
```
### Next.js routing documentation
```bash
# Find Next.js library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=nextjs&query=routing" | jq '.results[0].id'
# Fetch app router documentation
curl -s "https://context7.com/api/v2/context?libraryId=/vercel/next.js&query=app+router&type=txt"
```
### FastAPI dependency injection
```bash
# Find FastAPI library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=fastapi&query=dependencies" | jq '.results[0].id'
# Fetch dependency injection documentation
curl -s "https://context7.com/api/v2/context?libraryId=/fastapi/fastapi&query=dependency+injection&type=txt"
```
## Tips
- Use `type=txt` for more readable output
- Use `jq` to filter and format JSON responses
- Be specific with the `query` parameter to improve relevance ranking
- If the first search result is not correct, check additional results in the array
- URL-encode query parameters containing spaces (use `+` or `%20`)
- No API key is required for basic usage (rate-limited)
@@ -1,290 +0,0 @@
# External Library Registry
## Purpose
This file lists external libraries/frameworks that should use **ExternalScout** (via Context7) for live documentation instead of relying on potentially outdated training data.
## When to Use This
**ContextScout** checks this list when:
1. User asks about a library/framework
2. No internal context exists in `.opencode/context/development/frameworks/`
3. Query matches a library name below
**Action**: Recommend **ExternalScout** subagent
---
## Supported Libraries
### Database & ORM
#### Drizzle ORM
- **Aliases**: `drizzle`, `drizzle-orm`, `drizzle orm`
- **Docs**: https://orm.drizzle.team/
- **Context7**: `use context7 for drizzle`
- **Common topics**: schema organization, migrations, relational queries, transactions, TypeScript types
#### Prisma
- **Aliases**: `prisma`
- **Docs**: https://www.prisma.io/docs
- **Context7**: `use context7 for prisma`
- **Common topics**: schema, migrations, client, relations, TypeScript
---
### Authentication
#### Better Auth
- **Aliases**: `better-auth`, `better auth`, `betterauth`
- **Docs**: https://www.better-auth.com/docs
- **Context7**: `use context7 for better-auth`
- **Common topics**: Next.js integration, Drizzle adapter, social providers, session management, 2FA
#### NextAuth.js
- **Aliases**: `nextauth`, `next-auth`, `nextauth.js`
- **Docs**: https://next-auth.js.org/
- **Context7**: `use context7 for nextauth`
- **Common topics**: providers, callbacks, sessions, JWT
#### Clerk
- **Aliases**: `clerk`
- **Docs**: https://clerk.com/docs
- **Context7**: `use context7 for clerk`
- **Common topics**: authentication, user management, organizations
---
### Frontend Frameworks
#### Next.js
- **Aliases**: `nextjs`, `next.js`, `next`
- **Docs**: https://nextjs.org/docs
- **Context7**: `use context7 for nextjs`
- **Common topics**: App Router, Server Actions, Server Components, routing, middleware, API routes
#### React
- **Aliases**: `react`, `reactjs`, `react.js`
- **Docs**: https://react.dev/
- **Context7**: `use context7 for react`
- **Common topics**: hooks, components, state, effects, context
#### TanStack Query
- **Aliases**: `tanstack query`, `react query`, `@tanstack/react-query`
- **Docs**: https://tanstack.com/query/latest
- **Context7**: `use context7 for tanstack query`
- **Common topics**: useQuery, useMutation, prefetching, caching, Server Components
#### TanStack Router
- **Aliases**: `tanstack router`, `@tanstack/react-router`
- **Docs**: https://tanstack.com/router/latest
- **Context7**: `use context7 for tanstack router`
- **Common topics**: routing, type-safe routes, loaders, navigation
#### TanStack Start
- **Aliases**: `tanstack start`, `@tanstack/start`
- **Docs**: https://tanstack.com/start/latest
- **Context7**: `use context7 for tanstack start`
- **Common topics**: full-stack setup, server functions, file routing
---
### Infrastructure & Deployment
#### Cloudflare Workers
- **Aliases**: `cloudflare workers`, `cloudflare`, `workers`, `cf workers`
- **Docs**: https://developers.cloudflare.com/workers
- **Context7**: `use context7 for cloudflare workers`
- **Common topics**: routing, KV storage, Durable Objects, bindings, middleware
#### AWS Lambda
- **Aliases**: `aws lambda`, `lambda`, `aws λ`
- **Docs**: https://docs.aws.amazon.com/lambda
- **Context7**: `use context7 for aws lambda`
- **Common topics**: handlers, layers, environment variables, triggers, TypeScript
#### Vercel
- **Aliases**: `vercel`
- **Docs**: https://vercel.com/docs
- **Context7**: `use context7 for vercel`
- **Common topics**: deployment, environment variables, edge functions, serverless
---
### UI Libraries & Styling
#### Shadcn/ui
- **Aliases**: `shadcn`, `shadcn/ui`, `shadcn-ui`
- **Docs**: https://ui.shadcn.com/
- **Context7**: `use context7 for shadcn`
- **Common topics**: components, installation, theming, customization
#### Radix UI
- **Aliases**: `radix`, `radix ui`, `radix-ui`, `@radix-ui`
- **Docs**: https://www.radix-ui.com/
- **Context7**: `use context7 for radix`
- **Common topics**: primitives, accessibility, composition
#### Tailwind CSS
- **Aliases**: `tailwind`, `tailwindcss`, `tailwind css`
- **Docs**: https://tailwindcss.com/docs
- **Context7**: `use context7 for tailwind`
- **Common topics**: configuration, utilities, responsive design, dark mode
---
### State Management
#### Zustand
- **Aliases**: `zustand`
- **Docs**: https://zustand-demo.pmnd.rs/
- **Context7**: `use context7 for zustand`
- **Common topics**: store creation, selectors, middleware, TypeScript
#### Jotai
- **Aliases**: `jotai`
- **Docs**: https://jotai.org/
- **Context7**: `use context7 for jotai`
- **Common topics**: atoms, async atoms, utilities
---
### Validation & Forms
#### Zod
- **Aliases**: `zod`
- **Docs**: https://zod.dev/
- **Context7**: `use context7 for zod`
- **Common topics**: schema validation, TypeScript inference, parsing, refinements
#### React Hook Form
- **Aliases**: `react hook form`, `react-hook-form`, `rhf`
- **Docs**: https://react-hook-form.com/
- **Context7**: `use context7 for react hook form`
- **Common topics**: register, validation, errors, TypeScript
---
### Testing
#### Vitest
- **Aliases**: `vitest`
- **Docs**: https://vitest.dev/
- **Context7**: `use context7 for vitest`
- **Common topics**: configuration, testing, mocking, coverage
#### Playwright
- **Aliases**: `playwright`
- **Docs**: https://playwright.dev/
- **Context7**: `use context7 for playwright`
- **Common topics**: browser automation, testing, selectors, assertions
---
## Detection Patterns
ContextScout and ExternalScout should match queries containing:
- Library name (case-insensitive)
- Common variations (e.g., "next.js" vs "nextjs")
- Package names (e.g., "@tanstack/react-query")
**Examples**:
- "How do I use **Drizzle** with PostgreSQL?" → Match: Drizzle ORM
- "Show me **Next.js** App Router setup" → Match: Next.js
- "**TanStack Query** with Server Components" → Match: TanStack Query
- "**Better Auth** integration" → Match: Better Auth
---
## Query Optimization Patterns
### Drizzle ORM
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup/Installation | `PostgreSQL+setup+configuration+TypeScript+installation` |
| Modular schemas | `modular+schema+organization+domain+driven+design` |
| Relations | `relational+queries+one+to+many+joins+with+relations` |
| Migrations | `drizzle-kit+migrations+generate+push+PostgreSQL` |
| Transactions | `database+transactions+patterns+TypeScript` |
| Type safety | `TypeScript+type+inference+schema+types+inferInsert` |
### Better Auth
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `setup+configuration+Next.js+TypeScript+installation` |
| Next.js integration | `Next.js+App+Router+integration+setup+configuration` |
| Drizzle adapter | `Drizzle+adapter+PostgreSQL+schema+generation+configuration` |
| Social providers | `social+providers+OAuth+GitHub+Google+setup` |
| Email/password | `email+password+authentication+signup+signin` |
| Session management | `session+management+cookies+JWT+middleware` |
### Next.js
| User Intent | Optimized Query |
|-------------|-----------------|
| App Router | `App+Router+file+conventions+layouts+pages+routing` |
| Server Actions | `Server+Actions+form+mutations+revalidation+TypeScript` |
| Server Components | `React+Server+Components+async+data+fetching+patterns` |
| Dynamic routes | `dynamic+routes+params+TypeScript+generateStaticParams` |
| Middleware | `middleware+authentication+redirects+headers+cookies` |
| API routes | `API+routes+route+handlers+TypeScript+POST+GET` |
### TanStack Query
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `setup+QueryClient+provider+Next.js+TypeScript` |
| Data fetching | `useQuery+data+fetching+TypeScript+patterns+async` |
| Mutations | `useMutation+optimistic+updates+invalidation+TypeScript` |
| Prefetching | `prefetchQuery+Server+Components+hydration+Next.js` |
| Caching | `cache+configuration+staleTime+gcTime+invalidation` |
### Cloudflare Workers
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `getting+started+setup+TypeScript+wrangler+configuration` |
| Routing | `routing+itty-router+hono+request+handling` |
| KV storage | `KV+storage+key+value+bindings+TypeScript` |
| Durable Objects | `Durable+Objects+state+WebSockets+coordination` |
### AWS Lambda
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `getting+started+setup+TypeScript+handler+configuration` |
| Handlers | `handler+function+event+context+TypeScript+patterns` |
| Layers | `layers+dependencies+shared+code+deployment` |
| Environment variables | `environment+variables+secrets+configuration+SSM` |
---
## Adding New Libraries
To add a new library:
1. Add entry under appropriate category
2. Include: Name, aliases, docs link, Context7 command, common topics
3. (Optional) Add query optimization patterns
4. Update ExternalScout if needed (usually automatic)
**Template**:
```markdown
#### Library Name
- **Aliases**: `alias1`, `alias2`, `package-name`
- **Docs**: https://example.com/docs
- **Context7**: `use context7 for library-name`
- **Common topics**: topic1, topic2, topic3
```
---
## Usage by ExternalScout
ExternalScout uses this file to:
1. **Detect** which library the user is asking about
2. **Load** query optimization patterns for that library
3. **Build** optimized Context7 queries
4. **Fetch** live documentation
5. **Return** filtered, relevant results
-51
View File
@@ -1,51 +0,0 @@
# Context7 Skill Navigation
**Purpose**: Live documentation fetching for external libraries via Context7 API
---
## Structure
```
context7/
├── navigation.md # This file
├── README.md # Quick start and workflow
├── SKILL.md # Context7 API documentation
└── library-registry.md # Supported libraries and query patterns
```
---
## Quick Routes
| Task | Path |
|------|------|
| **Quick start** | `README.md` |
| **API reference** | `SKILL.md` |
| **Supported libraries** | `library-registry.md` (lines 18-181) |
| **Query patterns** | `library-registry.md` (lines 199-261) |
| **Add new library** | `library-registry.md` (lines 264-279) |
| **ExternalScout integration** | `README.md` (lines 9-26) |
---
## By Purpose
**Using Context7**:
- Quick start → `README.md`
- API details → `SKILL.md`
**Adding Libraries**:
- Template → `library-registry.md` (lines 272-279)
- Supported list → `library-registry.md` (lines 18-181)
**Integration**:
- ContextScout workflow → `README.md` (lines 54-73)
- ExternalScout subagent → `.opencode/agent/subagents/core/externalscout.md`
---
## Related
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
+218 -160
View File
@@ -3,58 +3,80 @@
Ты - профессиональный программист высокого уровня.
Ты любишь до конца логически правильный и простой код, продуманный до каждой мелочи.
Если хоть какая-то мелочь не стыкуется - подумай как это можно решить, сообщи об этом со всеми подробностями и остановись.
Если что-то получается нелогично или громоздко - хорошо подкмай как сделать просто и компактно. предложи варианты и остановись.
Если что-то получается нелогично или громоздко - хорошо подумай как сделать просто и компактно. предложи варианты и остановись.
Имей, загружай когда надо полный код нужных тебе функций/структур. Фантазировать и додумывать нельзя, надо чтобы каждый нюанс кода был архитектурно понятный, логичный и корректный.
Надо детально разобратсья в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
Старайся одно логичеси завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
Надо детально разобраться в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
Старайся одно логически завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
This file contains essential information for AI coding agents working in the uTun codebase.
## 📋 Quick Reference
## Quick Reference
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** TinyCrypt (AES-CCM, ECC)
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
## 🔧 Build Commands
## Build Commands
### Full Build
win: powershell build_full.bat (в точности как написано без дополнительных опций powershell, текущий каталог не важен)
### Full Build (Linux)
```bash
./configure # Configure build
make # Build everything
make install # Install (requires sudo)
./build.sh --full -j4 # autoreconf + configure + make
```
### Partial Builds
win: powershell -Command ".\build.bat" 2>&1 (аналог make если не изменен makefile.am)
логи сборки win: build_win.log
### Full Build (Windows/MSYS2)
```powershell
powershell build_full.bat # запускает bash build.sh --full через MSYS2 UCRT64
```
### Incremental Build
```bash
cd lib && make # Build only the library
cd src && make # Build only the main program
cd tests && make # Build only tests
./build.sh -j4 # make с авто-конфигурацией если надо
```
```powershell
powershell -Command ".\build.bat" 2>&1 # Windows, логи: build_win.log
```
### Clean Build
```bash
make clean # Clean object files
make distclean # Clean everything including configure files
make clean # Clean object files
make distclean # Clean everything including configure files
./build.sh --clean -j4 # Clean then rebuild
```
## 🧪 Test Commands
### Partial Builds
```bash
cd lib && make # Build only the library (libuasync.a)
cd src && make # Build only the main program
cd tests && make # Build only tests
```
### Direct Build (Windows, без autotools)
```bash
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
```
## Test Commands
### Run All Tests
win: powershell check.bat
```bash
make check # Run all tests via automake
make check # Run all tests via automake, логи в tests/logs/
```
```powershell
powershell check.bat # Windows, запускает каждый .exe из tests/
```
### Run Specific Test
```bash
cd tests/ && ./test_etcp_crypto # ETCP crypto test
cd tests/ && ./test_etcp_simple # Simple ETCP test
cd tests/ && ./test_ecc_encrypt # ECC encryption test
cd tests/
./test_etcp_crypto
./test_etcp_two_instances
./test_etcp_simple_traffic
./test_pkt_normalizer_etcp
./test_etcp_api
./test_ll_queue
./test_nat_detection
./test_bgp_route_exchange
```
### Run Single Test with Debug Info
@@ -65,7 +87,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
./my_test
```
## 💻 Code Style Guidelines
## Code Style Guidelines
### Naming Conventions
- **Functions:** `snake_case` - `etcp_connection_create()`, `sc_encrypt()`
@@ -85,7 +107,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
}
```
- **Comments:** Primary language is Russian for business logic, English for API docs
- **Line Length:** Aim for 80-100 characters
- **Line Length:** Aim for 80-100 characters, but can go up to 150 if logically coherent
### Include Order
```c
@@ -108,195 +130,231 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize: %s", err);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket created on port %d", port);
```
- Во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
## 🔐 Cryptography Guidelines
### Memory Management
- Use `u_malloc`/`u_calloc`/`u_realloc`/`u_free`/`u_strdup` from `lib/mem.h` (wrappers with leak tracking)
- Memory pools: `memory_pool_alloc()` / `memory_pool_free()` for hot-path allocations
- Queue entries: `queue_entry_new_from_pool()` / `queue_entry_free()` / `queue_dgram_free()`
## Cryptography Guidelines
### Key Sizes
| Constant | Value | Description |
|----------|-------|-------------|
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
| `SC_TAG_SIZE` | 16 | CCM auth tag |
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
### Using Secure Channel (secure_channel.h)
```c
// 1. Initialize context
struct SC_MYKEYS my_keys;
// ... fill keys ...
sc_init_local_keys(&my_keys, public_key_hex, private_key_hex);
// or sc_generate_keypair(&my_keys);
sc_context_t ctx;
sc_init_ctx(&ctx, &my_keys);
// 2. Set peer public key (for key exchange)
sc_set_peer_public_key(&ctx, peer_public_key, 0); // 0 = binary format
sc_set_peer_public_key(&ctx, peer_public_key, SC_PEER_PUBKEY_HEX); // 0=bin, 1=hex
// 3. Ready for encrypt/decrypt
sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ciphertext_len);
sc_decrypt(&ctx, ciphertext, ciphertext_len, plaintext, &plaintext_len);
// 4. Pubkey obfuscation (used in INIT/PING packets):
// salt(8) + XOR(SHA256(salt||peer_pubkey) || SHA256(peer_pubkey||salt), my_pubkey)
sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
```
### Important Notes
- **Nonce size:** Must be exactly 13 bytes for CCM mode (`SC_NONCE_SIZE = 13`)
- **Session key:** 16 bytes for AES-128 (`SC_SESSION_KEY_SIZE = 16`)
- **Tag size:** 8 bytes for authentication (`SC_TAG_SIZE = 8`)
- **Error codes:** Check return values, negative = error
- **Encryption:** 3-byte header (timestamp uint16_t + flag_up uint8_t) + data_len bytes encrypted
- **INIT packets:** Header + data encrypted, pubkey+salt block (SC_PUBKEY_ENC_SIZE bytes) appended unencrypted
- **Nonce:** Must be exactly 13 bytes for CCM mode
- **Error codes:** Check return values, negative = error (SC_OK=0, SC_ERR_* < 0)
## 🏗️ Architecture
## Debug System
### Debug Levels (по возрастанию)
`none < error < warn < info < debug < trace`
### Debug Categories (21 категория)
```
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
```
### Настройка отладки
- В конфиге: `debug = etcp=trace,config=info` (формат: `категория=уровень,...`)
- В коде: глобальный уровень и per-category уровни из `debug_config_t g_debug_config`
- Макросы: `DEBUG_ERROR(cat,fmt,...)` `DEBUG_WARN` `DEBUG_INFO` `DEBUG_DEBUG` `DEBUG_TRACE`
- `log_dump(prefix, data, len)` — hex dump в лог
### Dual Output
- Консоль и файл настраиваются раздельно (`debug_set_console_level`, `debug_set_file_level`)
- `debug_enable_file_output(path, truncate)` / `debug_disable_file_output()`
## Architecture
### Directory Structure
```
├── lib/ # Core libraries
├── src/ # Main source code
├── tests/ # Test programs
├── lib/ # Core libraries (13 .c + 14 .h)
├── src/ # Main source code (28 .c + 23 .h)
├── tests/ # 31+ test programs
├── doc/ # Technical Specifications
├── tools/ # Auxiliary tools
│ ├── etcpmon/ # GUI монитор ETCP
│ ├── proxy/ # UDP прокси для тестов
│ └── bping/ # BPing (bandwidth ping)
├── tinycrypt/ # TinyCrypt crypto library (external)
└── net_emulator/ # Network emulator
├── net_emulator/ # Network emulator (delays, loss, reordering)
└── c2/ # Test instance 2 (конфиг и бинарник для тестов)
```
### File Overview
**Core (src/)**
- `utun.c` - Main program entry point, CLI parsing, daemon mode
- `utun_instance.c/h` - Root instance lifecycle management
- `tun_if.c/h` - Simplified TUN interface (init/write/close)
- `utun_instance.c/h` - Root instance lifecycle, config loading, all submodule init
- `tun_if.c/h` - TUN interface API (init/write/close, cross-platform)
- `tun_linux.c` `tun_freebsd.c` `tun_windows.c` - Platform-specific TUN implementations
- `tun_route.c/h` - TUN routing table sync
**Network Stack (src/)**
- `etcp.c/h` - ETCP protocol implementation (TCP-like with crypto)
- `etcp_connections.c/h` - Socket and link management
- `etcp_loadbalancer.c/h` - Multi-link load balancing
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly
- `routing.c/h` - Routing table management
- `etcp.c/h` - ETCP protocol implementation (inflight queues, retrans, ACK, RTT/jitter)
- `etcp_api.c/h` - ETCP public API (send/recv/bind callbacks)
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
- `etcp_debug.c/h` - ETCP packet dump/formatting
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
- `packet_dump.c/h` - Packet hex dump utility
- `firewall.c/h` - Firewall rules (per-interface filtering)
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
**Routing (src/)**
- `routing.c/h` - Routing table management (local routes)
- `route_lib.c/h` - Routing library utilities
- `route_bgp.c/h` - BGP-style route exchange between peers
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
- `route_node.c/h` - Route node (peer) management
**Crypto (src/)**
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
- `crc32.c/h` - CRC32 checksums
**NAT (src/)**
- `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine
- `nat_transport.c/h` - NAT transport layer (packet relay)
**Config (src/)**
- `config_parser.c/h` - INI-style config file parsing
- `config_updater.c/h` - Config file modification utilities
**Control (src/)**
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
**Libraries (lib/)**
- `u_async.c/h` - Async event loop (epoll/poll, timers)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks
- `memory_pool.c/h` - Fast object pool allocator
- `debug_config.c/h` - Debug logging system
- `timeout_heap.c/h` - Min-heap for timer management
- `sha256.c/h` - SHA256 hashing
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
- `memory_pool.c/h` - Fast object pool allocator (pre-allocated blocks)
- `debug_config.c/h` - Debug logging system (levels, categories, dual output, runtime config)
- `timeout_heap.c/h` - Min-heap for timer management (used by u_async)
- `sha256.c/h` - SHA256 hashing (fallback; OpenSSL preferred via USE_OPENSSL)
- `mem.c/h` - Memory wrappers with leak tracking (`u_malloc`/`u_free`/`u_calloc`/`u_strdup`)
- `serialize.c/h` - Binary serialization utilities
- `swm_min.c/h` - Sliding window minimum (for RTT min tracking)
- `getmyip.c/h` - Get local IP / default route detection
- `myip.c` - My IP helper (no header)
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
**Tests (tests/)**
- `test_etcp_*.c` - ETCP protocol tests
- `test_crypto.c` - TinyCrypt AES/CCM tests
- `test_ecc_encrypt.c` - ECC encryption tests
- `test_ll_queue.c` - Queue functionality tests
- `bench_*.c` - Performance benchmarks
**External**
- `tinycrypt/` - TinyCrypt library (AES, ECC, SHA256)
**Memory Pools in UTUN_INSTANCE:**
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
- `pkt_pool` — для struct ETCP_DGRAM (сетевые пакеты на отправку/приём)
- `ack_pool` — для struct ACK_PACKET (подтверждения приёма)
### Key Components
- **UASYNC:** Asynchronous event loop for sockets and timers
- **LL_QUEUE:** Lock-free lockstep queue for packet handling
- **Memory Pool:** Fast allocation for network packets
- **ETCP:** Extended TCP protocol with crypto and reliability
- **Secure Channel:** AES-CCM encryption with ECC key exchange
- **UASYNC:** One per thread. Async event loop (epoll/poll), timers via timeout_heap
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
## 🐛 Debugging Tips
## Queue Usage Rules
### Enable Debug Output
```c
// In source files, define debug before including headers
#define DEBUG_CATEGORY_YOUR_MODULE 1
```
### Запись в очередь
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
### Common Build Issues
1. **Missing headers:** Check include paths in Makefile.am
2. **Undefined references:** Ensure source files are listed in Makefile.am
3. **Link errors:** Check function declarations match definitions
### Чтение из очереди
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
- Внутри коллбэка ОБЯЗАТЕЛЬНО: `queue_data_get(q)` → обработать элемент → `queue_resume_callback(q)`
- Без вызова `queue_resume_callback` очередь навсегда застрянет
## 📦 Dependencies
### Поиск
- `queue_data_put_with_index(q, entry)` — добавляет с индексом для быстрого поиска (offset/size заданы при queue_new)
- `queue_find_data_by_index(q, key)` — поиск по индексу через хеш-таблицу (размер ключа из q->index_size)
### Build Dependencies
- autoconf, automake, libtool
- gcc with C99 support
- pthread library
- OpenSSL/crypto library (for SHA256)
## u_async Rules
- Нельзя использовать в одном потоке несколько u_async. Один поток = всегда 1 uasync instance
- Нельзя использовать sleep/usleep если есть uasync. Нужно использовать `uasync_set_timeout`.
- Таймеры: `uasync_set_timeout(ua, timeout_tb, arg, callback, name)` — timebase units (0.1ms)
Возвращает `void*` handle, отмена: `uasync_cancel_timeout(ua, handle)`.
### Runtime Dependencies
- TUN/TAP interface support (Linux kernel module)
- libcrypto (usually installed by default)
- Proper network configuration for testing
## Config Rules
- В серверном конфиге только собственные ключи и нет секций `[client]`
- В клиентском конфиге есть собственные ключи и pubkey каждого сервера в секции `[client]`
## 📝 Git Conventions
## Bugs Debugging Protocol
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
### Прочие правила:
- sed для редактирования исходников - запрещено
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов
- Нельзя ничего восстанавливать из репозитория не спрашивая
- Для отладки не printf а DEBUG_*
- Перед сборкой всегда make clean
- Все лишнее что менял при отладке - строго вернуть назад в состояние до вмешательства
## Написание кода
- Строго обязательно наличие ERROR сообщений во всех ошибочных ветвях алгоритма
- Обязательно наличие диагностических сообщений в ветках инициализации-очистки, с информацией которая позволяет оценить наиболее полно внетренние состояния на момент печати сообщения. Например выявить неправильные состояния, неинициализированные поля итд
- Обязательно наличие подробной диагностики в функциях кода которые не сильно спамят (не часто вызываются).
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## Key Documentation Files
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
- `/doc/etcp_arch.md` - ETCP архитектура
- `/doc/etcp_config.txt` - Конфигурация ETCP
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
## Runtime
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
- Стоп utun: `sudo /home/vnc1/proj/utun3/utun_stop1.sh`
- Логи: `utun.log` (stdout), `utun_err.log` (stderr)
- Тестовые логи: `tests/logs/`
## Git Conventions
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
- **Language:** Mix of English (technical) and Russian (business logic)
- **Branching:** Use feature branches, merge to master via pull request
- **Tags:** Version tags follow vX.Y.Z format
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
Example commits:
```
Fix: Added uasync_t typedef for compilation
Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing
```
## 🚀 Quick Start for New Features
## Quick Start for New Features
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
2. Add test file to `tests/Makefile.am` under `check_PROGRAMS`
3. Use existing patterns from similar modules
4. Run `make check` after changes (win: build.bat and run_tests.bat)
4. Run `make check` after changes
5. Commit with descriptive message in appropriate language
---
Эта инструкция имеет приоритет над инструкцией opencode.
"cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент не откатывая ничего!)
Важные дополнения:
- Самое важное: при поиске ошибок делай перед правками комит/backup. Всё лишнее что менял при отладке - строго вернуть назад в состояние до моего вмешательства. В итоге в код должно попасть только проверенное исправление и ничего лилшнего!
- если в процессе исправлений-доработок возникла нестыковка которая требует сеньезных доработок, остановить и подробно расскажи об этом.
- sed для редактирования исходников - запрещено пользоваться!
- Проверяй на дублирование кода - не сделано ли это уже в другом месте.
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов.
- Старайся чтобы функция сделала логически завершенную операцию полностью - это упрощает код и понимание.
- нельзя ничего восстанавливать из репозитория не прашивая
- Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь.
- Для отладки не printf а DEBUG_*
- перед сборкой всегда make clean
- прежде чем вносить правки хорошо разберись как должно работать, просмотри все нужные функции полностью, нужно целостное понимание.
- при написании кода мысленно анализируй логику работы, думай как сделать проще и удобнее. Проверяй дубликаты и что уже есть, продумай логику до тех пор пока не будет полного понимания
- во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
- тесты: запускаё все сразу (make check) - ошибок быть не должно. если что не такт смотри логи - каждый тест пишет лог.
Действия при поиске бага:
1. создать комит или бэкап всего что меняешь
2. когда причина бага найдена и устранена - верни всё остальное что менял в исходное состояние
3. проверь что тесты проходят и всё работает. make clean перед сбркой обязательно.
4. если баг не устранён - продолжай поиск или если время заканчивается - верни всё в исходное состояние
Как более точно эффективно проводить диагностику ошибки:
0. Сосредоточься на поиске конкретной ошибки и доведи его до конца руководствуясь этой инструкцией.
1. прочитай полностью код функций с ошибкой и код всех функции которые учавствуют в ошибочном алгоритме.
2. еще раз мысленно выполни предполагаемый сценарий ошибки (если чего-то не хватает до полной картины - обязательно дочитывай все ветви функций по ходу: нельзя додумывать - нужна точность) и убедись что:
- ты точно понимаешь как алгоритм прихдит к ошибке
- все функции которые учавствуют в сценарии ошибки проанализированы и их поведение проверено и понятно
- последовательно отсекай логически законченные блоки которые проверены и точно правильно работают
- если ты полностью проанализировал функцию и к ней нет описания или описание неточное (неполное), и функция работает логически корректно - поправь описание. Если функция работает логически неверно - добавь запись об этом в todo.txt
3. Если исправление как либо меняет поведение функции:
- просмотри где используется эта функция
- убедись что изменение поведения не повлияет на остальные места где функция используется. особенно важный пункт для библиотек и модулей коммуникаций
Работа с очередями.
- Запись в очередь: очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой. Пользуйся наблюдателем queue_wait_threshold, он специально сделан для добавления в очередь.
- Чтение из очереди: пользуйся queue_set_callback: при вызове callback , обработай один или несколько элементов, потом вызови resume_callback.
Работа с u_async
- нельзя использовать в одном потоке несколько u_async. один поток = всегда 1 uasync instance
- нельзя использовать sleep/usleep если есть uasync. Нужно использовать uasync_set_timeout.
Конфиги:
- в серверном только собственные ключи и нет секций [client]
- в клиентском есть собственные ключи и pubkey каждого сервера в секции [client]
тех задания для реализации в каталоге /doc.
/doc/etcp_protocol.txt - основной протокол (похож на TCP+QUIC, поддеиживает шифрования, load balancing multi-link, работу а неустойчивых каналах, утилизацию полосы и недопущение перегрузки каналов связи)
- реализация в /src/etcp*.c/h
запуск utun от root (для tun) - /home/vnc1/proj/utun3/utun_start.sh
стоп utun: sudo /home/vnc1/proj/utun3/utun_stop1.sh
логи - utun.log
*Last updated: 2026-02-13 - c_util fully functional with toc/description/show/edit commands*
+253
View File
@@ -0,0 +1,253 @@
========================================================================
uTun3 AUDIT FINDINGS — Сводный отчёт
========================================================================
Аудит от 14.05.2026 — обход основных модулей
(use-after-free, out-of-bounds, утечки, reentrancy)
========================================================================
HIGH (19 проблем)
========================================================================
1. u_async.c:137-165 use-after-free
Частичный успех u_realloc в socket_array_add_internal: если часть
реаллоков прошла, а часть нет — старые массивы уже освобождены,
новые не выделены, sa->sockets/sa->fd_to_index/sa->index_to_fd
указывают на освобождённую память.
2. u_async.c:842,1167 логическая ошибка
process_posted_tasks никогда не вызывается на Linux.
В epoll-пути: drain_wakeup_pipe не вызывает handle_wakeup/process_posted_tasks.
В poll-пути: wakeup-слот (i==0) обрабатывается с drain_wakeup_pipe и continue,
минуя зарегистрированный коллбэк.
Следствие: uasync_post из другого потока не доставляет задачи — posted_tasks
накапливаются бесконечно.
3. u_async.c:1383,1398,1418 утечка памяти
При сбое создания wakeup (pipe/socket) в uasync_create не освобождаются:
ua->sockets, ua->timeout_heap, ua->timeout_pool, ua->epoll_fd.
4. memory_pool.c:51-69 double-free
Нет детекта double-free. Повторный free создаёт цикл во freelist,
memory_pool_alloc выдаёт один и тот же блок двум потребителям.
5. etcp.c:679-683 утечка памяти
input_queue_cb: при исчерпании inflight_pool вызывается
queue_entry_free(in_pkt) без queue_dgram_free(&in_pkt->ll).
dgram из data_pool утекает.
6. etcp_connections.c:87,876-926 use-after-free
burst_resp_timer не отменяется в etcp_link_close.
Коллбэк burst_resp_timeout_cb срабатывает на освобождённом link.
7. etcp_connections.c:846-854 use-after-free
stats_timer не отменяется при провале insert_link в etcp_link_new.
link_stats_timer_cb срабатывает на освобождённом link.
8. etcp_connections.c:879-886 use-after-free
Ветка conn==NULL в etcp_link_close пропускает отмену ВСЕХ таймеров
(stats_timer, init_timer, shaper_timer, keepalive_timer, burst_resp_timer).
Любой из них сработает на освобождённом link.
9. etcp_connections.c:1388,1531,1561 out-of-bounds read
INIT request парсинг: проверка pkt_len < 12 должна быть pkt_len < 26
(23 байта заголовка). Пакет с длиной 12-25 пройдёт проверку,
но читает за границей расшифрованных данных.
10. pkt_normalizer.c:33 null deref
if (!pn) pn->alloc_errors++ — разыменование NULL после u_calloc.
11. pkt_normalizer.c:300 null deref
pn_buf_renew в цикле without проверки возврата → memcpy(NULL, ...)
при сбое memory_pool_alloc.
12. eim_nat.c:80-93 логическая ошибка
Переполнение uint16_t порта: next_port с 65535→0, затем порты
ниже port_start выделяются (включая порт 0). Соединения нерабочие.
13. eim_nat.c (весь файл) логическая ошибка
Нет механизма истечения/удаления NAT-записей. Таблица навсегда
исчерпывается при долгой работе, eim_nat_egress начинает отказывать.
14. route_bgp.c:749,762-766 утечка памяти
route_bgp_process_nodeinfo: при пересоздании nodeinfo1 старая
очередь paths сохраняется в локальную переменную, но затем
перезаписывается новой queue_new() — утечка.
15. route_bgp.c:904-907 утечка памяти
route_bgp_send_nodeinfo: при провале etcp_send не освобождаются
p (u_malloc) и e (queue_entry_new).
16. route_ping.c:126-134 утечка памяти
route_ping_cancel_for_conn: nat_check_arg (p->arg, u_calloc) не
освобождается. В нормальном и таймаутном путях коллбэк освобождает,
но cancel-путь коллбэк не вызывает.
17. ll_queue.c:321,439 reentrancy
Синхронный вызов коллбэка при первом queue_data_put (count==1)
без отложенного возобновления через uasync → рекурсивный
ack_timeout_check с устаревшим указателем current.
18. secure_channel.c:379,711 потенциальный OOB write
sc_encrypt пишет в caller buffer без параметра capacity.
Контракт API опасен: вызывающий должен сам гарантировать размер буфера.
19. secure_channel.c:432,756 потенциальный OOB write
sc_decrypt — аналогично, нет проверки capacity выходного буфера.
========================================================================
MEDIUM (18 проблем)
========================================================================
20. u_async.c:591-639 use-after-free
Stale handle в uasync_cancel_timeout/uasync_call_soon_cancel.
Нет инвалидации (generation counter), пул может переиспользовать память.
21. memory_pool.c:8,38,61,95 race condition
Глобальная g_total_free изменяется без блокировок, общая для всех пулов.
22. memory_pool.c:51-69 потенциальный OOB write
Нет проверки принадлежности блока пулу. Free в чужой пул вызывает
memset с чужим object_size — частичная очистка или запись за границу.
23. timeout_heap.c:43-45 утечка / несоответствие документации
Документация обещает free() при free_callback==NULL, код этого не делает.
24. etcp.c:718-763 reentrancy
Вложенный ack_timeout_check (через input_send_q_cb→etcp_request_pkt→
wait_ack_cb) обрабатывает не тот entry, т.к. outer loop использует
current, который мог быть перемещён/удалён внутренним вызовом.
25. etcp.c:747,944 утечка / дрейф счётчиков
Непроверенный queue_data_put_with_index: при провале счётчики
(retransmissions_count, inflight_bytes) не откатываются.
26. etcp_connections.c:1731,1763 out-of-bounds read
INIT_RESPONSE парсинг: нужно >=19 байт, проверяется только >=3.
27. etcp_connections.c:748-753 use-after-free + бесконечный цикл
etcp_socket_remove: при link->conn==NULL remove_link пропускается,
num_channels не уменьшается, цикл перечитывает освобождённый conn->links[0].
28. etcp_connections.c:1736-1738 логическая ошибка
link_status перезаписывается значением remote_keepalive, игнорируя
локальный recv_keepalive. Временно помечает link UP даже когда
локальный приём мёртв.
29. pkt_normalizer.c:43 integer overflow/underflow
frag_size = mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE.
При mtu < 158 беззнаковое вычитание даёт ~65379 → огромные буферы.
30. pkt_normalizer.c:240 утечка памяти
При провале queue_data_put фрагмент и dgram не освобождаются.
31. eim_nat.c:97,173 null deref
Нет проверки ctx->initialized/ctx->table в egress/ingress.
32. eim_nat.c:268,293 логическая ошибка
Статические пробросы (port forward) вне port range недоступны.
33. secure_channel.c:66 key hygiene
Нет sc_free_ctx — сессионный ключ не зануляется при уничтожении контекста.
34. secure_channel.c:322,620 key hygiene
ECDH shared secret остаётся на стеке незанулённым после
sc_derive_session_key.
35. secure_channel.c:352,680 nonce entropy
При постоянном провале random_bytes seed остаётся 0 (static init).
Nonce становится полностью предсказуемым (counter+timestamp).
36. route_bgp.c:1122 утечка памяти
route_bgp_send_nat_info: etcp_send без проверки → pkt/e утекают при провале.
37. route_bgp.c:1157 утечка памяти (аналогично #36)
route_bgp_send_nat_check_req: etcp_send без проверки.
38. config_parser.c:1055-1066 логическая ошибка
update_config_keys дописывает [global] секцию в конец файла.
При повторном вызове получается дубликат секции, парсер может сбойнуть.
========================================================================
LOW (13 проблем)
========================================================================
39. u_async.c:1594-1617 утечка (abort)
abort() в destroy до освобождения posted_tasks и мьютекса.
40. u_async.c:1608 race condition
posted_tasks_head читается без posted_lock в destroy.
41. memory_pool.c:47 truncation
size_t → uint32_t в u_calloc_impl (object_size > UINT32_MAX).
42. memory_pool.h:16 signedness
int free_count может обернуться при двойном освобождении.
43. timeout_heap.c:31 dead code
freed_count никогда не обновляется, timeout_heap_get_freed_count не реализован.
44. timeout_heap.c:66-67 integer overflow
capacity*2 и sizeof(TimeoutEntry)*new_cap могут переполниться.
45. ll_queue.c:106-127 dangling pointer
queue_free освобождает hash_table, но не чистит entry->hash_next.
46. pkt_normalizer.c:374 десинхронизация потока
При провале ll_alloc_lldgram 2 байта заголовка уже consumed,
но фрагмент сброшен → потеря данных.
47. eim_nat.c:105,181 unaligned access (ARM)
*(uint16_t*)(ip_data + 6) — невыровненный доступ на ARM.
48. secure_channel.c:108-131 избыточность
CRC32 поверх CCM (аутентифицированное шифрование) избыточен.
49. secure_channel.h:51-52 dead code
Неиспользуемые поля send_nonce[13]/recv_nonce[13].
50. config_parser.c:312 undefined behavior
~0U << (32 - cidr) при cidr==0 — сдвиг на ширину типа.
51. route_ping.c:227 integer overflow
uint16_t avg_rtt = timeout_ms * 10 — переполнение при timeout_ms > 6553.
========================================================================
Файлы без ошибок
========================================================================
- route_node.c — чисто
- tun_if.c — чисто
- etcp_api.c — только порядок queue_entry_free/dgram_free (LOW, см. etcp.c Issue #7 старого аудита)
- etcp_loadbalancer.c — не аудирован (250 строк)
========================================================================
Рекомендуемый порядок исправления
========================================================================
1. КРИТИЧЕСКИ ФУНКЦИОНАЛЬНЫЕ:
u_async.c #2 (posted_tasks не работают на Linux)
u_async.c #3 (утечки при сбое старта)
2. USE-AFTER-FREE:
etcp_connections.c #6,#7,#8,#5 (висячие таймеры)
u_async.c #1 (частичный realloc)
ll_queue.c #17 (reentrancy)
3. УТЕЧКИ ПАМЯТИ:
etcp.c #5 (dgram из data_pool)
route_bgp.c #14,#15,#36,#37
route_ping.c #16
4. OOB / NULL DEREF:
etcp_connections.c #9,#26 (INIT bounds)
pkt_normalizer.c #10,#11 (null deref)
5. ЛОГИКА:
eim_nat.c #12,#13 (порты + expiry)
memory_pool.c #4 (double-free)
config_parser.c #38 (дубликат секции)
+28
View File
@@ -0,0 +1,28 @@
# Bootstrap Makefile for utun
# All build artifacts (.o) go to build/ directory
# Final binary (utun) is copied to project root
BUILD_DIR = build
all:
@mkdir -p $(BUILD_DIR)
@if test ! -f $(BUILD_DIR)/config.status || test ./configure -nt $(BUILD_DIR)/config.status; then \
echo "Configuring in $(BUILD_DIR)/..."; \
cd $(BUILD_DIR) && ../configure; \
fi
@$(MAKE) -C $(BUILD_DIR)
clean:
-rm -rf $(BUILD_DIR)
distclean: clean
-rm -f config.status config.log config.h stamp-h1
-rm -rf autom4te.cache/
check: all
@$(MAKE) -C $(BUILD_DIR) check
install:
@$(MAKE) -C $(BUILD_DIR) install
.PHONY: all clean distclean check install
+1 -19
View File
@@ -1,26 +1,8 @@
# Main Makefile.am for utun - root directory configuration
# This is the root Makefile.am - tests are defined in tests/Makefile.am
# Basic configuration for root directory
# Order matters: lib -> src -> tests (tests depend on src objects)
SUBDIRS = lib src tests
# Build directory for out-of-tree builds
BUILD_DIR = build
# Custom target to run full build in separate directory
.PHONY: fullbuild
fullbuild:
@echo "Building in $(BUILD_DIR)/ directory..."
@$(MKDIR_P) $(BUILD_DIR)
@if test ! -f $(BUILD_DIR)/Makefile; then \
echo "Running configure in $(BUILD_DIR)..."; \
cd $(BUILD_DIR) && $(top_srcdir)/configure; \
fi
@$(MAKE) -C $(BUILD_DIR) all
@echo "Build complete. Binaries copied to project root."
# Clean build directory
clean-local:
-rm -rf $(BUILD_DIR)
-rm -rf build/
Regular → Executable
+13 -27
View File
@@ -69,17 +69,16 @@ fi
if [ "$CLEAN" -eq 1 ]; then
echo "Cleaning build..."
make clean 2>/dev/null || true
rm -f config.status config.h
fi
# Full rebuild: clean everything and regenerate
# Full rebuild: regenerate build system
if [ "$FULL_REBUILD" -eq 1 ]; then
echo ""
echo "======================================"
echo "FULL REBUILD: autoreconf + configure"
echo "FULL REBUILD: autoreconf"
echo "======================================"
echo ""
# Check for autotools
if ! command -v autoreconf &> /dev/null; then
echo "Error: autoreconf not found."
@@ -90,29 +89,24 @@ if [ "$FULL_REBUILD" -eq 1 ]; then
fi
exit 1
fi
# Clean generated files
echo "Cleaning generated files..."
make distclean 2>/dev/null || true
rm -f config.status config.h Makefile */Makefile
# Regenerate build system
rm -rf build/
# Regenerate build system (root Makefile is hand-written, not generated)
echo ""
echo "Running autoreconf -fi..."
autoreconf -fi 2>&1 | tee autoreconf.log
# Configure
echo ""
echo "Running configure..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
echo ""
echo "======================================"
echo "Build system regenerated successfully"
echo "======================================"
echo ""
else
# Quick/incremental build: only configure if needed
# Quick/incremental build: generate configure if needed
if [ ! -f ./configure ]; then
echo "Configure script not found. Generating..."
if ! command -v autoreconf &> /dev/null; then
@@ -126,21 +120,13 @@ else
fi
autoreconf -fiv
fi
# Run configure if needed
if [ ! -f config.status ] || [ config.status -ot configure ]; then
echo "Configuring build..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
else
echo "Using existing configuration."
fi
fi
# Build
# 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_linux.log
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
if [ ${PIPESTATUS[0]} -eq 0 ]; then
BUILD_SUCCESS=1
fi
@@ -148,10 +134,10 @@ fi
if [ $BUILD_SUCCESS -eq 1 ]; then
echo ""
echo "Build completed successfully!"
echo "Binary: src/utun"
echo "Binary: utun"
else
echo ""
echo "ERROR: Build failed!"
echo "Check build_linux.log for details"
echo "Check build.log for details"
exit 1
fi
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
pkg install -y autoconf openssl
#pkg install -y autoconf openssl
mkdir -p m4
set -e
+137 -74
View File
@@ -7,120 +7,183 @@
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия".
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку.
как формируется пакет:
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку
1. выбираем канал передачи (etcp_loadbalancer_select_link)
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем
если пакет найден:
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
2. в конец добавляем секцию 0x00 с данными пакета
3. если пакет найден или есть неподтверждённые ACK:
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
2. в конец добавляем секцию 0x00 с данными пакета
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен
Функция прикрепления опциональных секций:
- запрашивает выбор канала передачи.
- добавляет накопившиеся ACK
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets)
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем).
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки.
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
пересканирование inflight:
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight.
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени)
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts).
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное]
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет.
- если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди
inflight - два списка (ll_queue):
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку)
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack)
доп. свойства пакетов:
- указатель на ETC_LINK последней отправки
- timestamp последней отправки
- число отправок
- число запросов переповтора
inflight - две очереди (ll_queue):
- input_send_q: список пакетов ожидающих отправку
- input_wait_ack: список неподтвержденных пакетов ожидающих ack
При отправке пакет перемещается из input_send_q в input_wait_ack.
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q.
доп. свойства пакетов (struct INFLIGHT_PACKET):
- указатель на ETC_LINK последней отправки (last_link)
- timestamp последней отправки (last_timestamp)
- число отправок (send_count)
- send_hist[8] - история через какие линки отправлялся (индекс линка)
- inflight_bytes/inflight_packets per-link отслеживается в ETCP_LINK
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight.
optimal_inflight расчитывается из RTT и bandwidth линка.
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом:
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась).
после передаём пачку пакетов только по этому линку со следующими таймингами:
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов.
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts].
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth.
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit.
bandwidth по каждому линку адаптивно подстраиваем:
Периодически в stats_timer_cb собираем статистику окна (rtt_avg10, ретрансмиссии, переданные пакеты).
BW рассчитывается как: inflight_lim_bytes * 8 / rtt_min (в секундах) * 8 / 1024 * 1.3 (Kbits/sec).
Размер inflight_lim_bytes плавно подстраивается вверх/вниз в зависимости от того, растёт ли RTT.
Burst-измерения bandwidth (0x07, 0x08) зарезервированы, но на данный момент не реализованы.
**** Формат кодограмм для etcp.c/h ****
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags]
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
- flags: bit0 = up/down (recv_keepalive)
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ
2. Опциональные секции (одна или несколько) добавляются в etcp.c:
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags/flag_up]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке).
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных.
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка):
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: количество пар ID+timestamp (1-32)
- last_delivered_id: последний ID, доставленный получателю
- last_rx_id: последний полученный ID (для синхронизации прогресса)
в) channel timestamp (добавляется после выбора канала передачи):
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low]
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно).
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами]
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count]
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета)
- last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд)
- rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне)
- seq: uint32_t — номер подтверждаемого пакета
- recv_ts: uint16_t — локальный timestamp приёма пакета (в единицах 0.1 мс)
- dly: uint16_t — задержка между приёмом пакета и отправкой ACK (для расчёта RTT)
Каждая запись = 8 байт.
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные):
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)]
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time)
(оценка текущего времени на передающей стороне в момент отправки)
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp
(относительное смещение между локальным и удалённым временем)
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки)
и если прошло не более ~100ms (dt < 1000000 таймбаз).
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID 4 байта] [данные...] - Данные пакета
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit]
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?]
[0x00] [ID 4 байта LE] [данные...]
- ID: uint32_t, 32-битный циклический номер пакета
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные),
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний.
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции).
цель протокола:
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT)
Установка подключения для канала (реализуется в etcp_connections.c/h):
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи).
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
- RTT последнего пакета и время отправки последнего пакета
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики:
- количество отправленных в пакетах байтах
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- общее количество отправленных/принятых байт
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент
Планировщик/балансир каналов (etcp_loadbalancer.c/h):
для каждого канала ограничение полосы с тестированием отклика:
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков)
Ограничение реализуем следующим образом:
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта).
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности.
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время)
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues]
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи");
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
процесс установки соединения:
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK)
- по таймауту повторяем init запросы, ведем счетчик запросов init.
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1.
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения).
процесс установки соединения (рукопожатие):
- клиент отправляет init запрос и выставляет таймаут
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive.
- сервер при получении init запроса: если линк существует и session_id совпал — отвечает без сброса (0x05); если session_id новый — выполняет etcp_conn_reinit и отвечает со сбросом (0x03).
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT со сбросом (0x02); если link_state=2 (reconnect) — без сброса (0x04).
link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
**** Формат кодограмм для etcp_connections.c/h ****
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат]
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса):
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [recovery high] [recovery low] [my link_id 1 байт] [my public key (64 байта, не шифруется)]
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы.
- link_id: локальный идентификатор канала (0-255), назначается отправителем для идентификации канала
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM)
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04):
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low] [my link_id 1 байт] [peer ipv4 4 байта] [peer port 2 байта]
- Подтверждение инициализации (канал успешно создан, можно начинать обмен)
- link_id: локальный идентификатор канала (0-255) отправителя ответа
- peer ip.port: возвращаем ip:port с которого пришел init запрос (чтобы клиент узнал свой external ip:port если за nat)
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда одна секция-тип пакета). В обязательном заголовке (timestamp+flag_up) ID не используется.
Типы пакетов:
0x02 — ETCP_INIT_REQUEST (со сбросом etcp сессии)
0x03 — ETCP_INIT_RESPONSE (подтверждение со сбросом)
0x04 — ETCP_INIT_REQUEST_NOINIT (без сброса)
0x05 — ETCP_INIT_RESPONSE_NOINIT (подтверждение без сброса)
0x06 — ETCP_PING (проверка доступности)
0x07 — ETCP_PONG (ответ на пинг)
1) Init запрос (0x02 / 0x04):
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [keepalive_interval(2)]
[recovery_interval/100(2)] [local_link_id(1)] [sock_id(1)] [only_local(1)] [type(1)]
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
- code: 0x02 (со сбросом) или 0x04 (без сброса)
- node_id: 64 бита, идентификатор узла (big-endian)
- session_id: 32 бита, случайный ID сессии генерируется клиентом (big-endian)
- mtu_local: uint16_t локальный MTU
- keepalive_interval: uint16_t интервал keepalive в мс
- recovery_interval: uint16_t = recovery_interval/100 (в таймбазах 0.1мс / 100)
- 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)
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
в noencrypt_len байтах (не попадает под AES-шифрование)
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт.
Между нодами только одно подключение, но можно добавлять каналы.
2) Init подтверждение (0x03 / 0x05):
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [local_link_id(1)]
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2)]
[random_padding...]
- code: 0x03 (со сбросом) или 0x05 (без сброса)
- node_id: 64 бита, big-endian
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует)
- mtu_local: uint16_t локальный MTU сервера
- local_link_id: uint8_t идентификатор канала сервера
- remote_socket_id: uint8_t (копируется из запроса)
- only_local: uint8_t флаг сервера
- type: uint8_t тип сокета сервера
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
- peer_port: 2 байта (порт клиента как его видит сервер)
- random_padding: случайные байты
3) PING (0x06):
[0x06] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...]
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
Отправляется с отдельным crypto-контекстом (не связан с ETCP-сессией).
Используется для проверки доступности узла и NAT-тестирования.
4) PONG (0x07):
[0x07] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...]
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
Ответ на PING. nonce копируется из запроса для сопоставления.
При получении init получатель пакета должен:
- reset ETCP_LINK с этим ip_port если он есть
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации)
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - проверить ключи на совпадение.
приём кодограмм из UDP выглядит так:
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
Keepalive: пакет без секций (только timestamp+flags)
Keepalive: пакет без секций (только timestamp+flag_up в шифрованном заголовке, data_len=0).
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика.
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента).
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты).
Клиент: если все линки =down то начинается процедура восстановления связи:
- клиент посылает init без сброса
- сервер как получает init без сброса отправляет init подтверждение: со сбросом или без сброса в зависимости от состояния init его соединения (инициализировано - то без сброса).
- клиент посылает init без сброса (0x04), link_state=2
- сервер как получает init проверяет session_id:
- если session_id совпадает — шлёт подтверждение без сброса (0x05)
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03)
+344
View File
@@ -0,0 +1,344 @@
Архитектура P2P Direct Connection (установка оптимального подключения между узлами)
Цель: в большой сети узлов каждый узел умеет находить быстрые подключения
к любому другому узлу — либо прямое (direct), либо через минимальное число
промежуточных узлов с минимальным суммарным RTT. Оба узла пробуют доступные
варианты пингом и выбирают лучший.
=== Что уже есть в коде (используем, не дублируем) ===
1. NODEINFO уже содержит поле tranzit_nodes + структуру NODEINFO_TRANZIT_NODE
(node_id, rtt, link_q) — "лучшие транзитные узлы, выбирается/обновляется узлом",
но пока не заполняется и не используется.
2. Пинг-инфраструктура: route_ping_send_req_addr (запрос удалённого пинга через
третий узел), route_ping_handle_resp (приём результата). Также есть
etcp_send_ping_to_socket — прямой пинг с конкретного сокета на конкретный адрес
с pubkey для шифрования.
3. NAT-детекция уже работает: проверка типа NAT через третий узел, обмен NAT_INFO.
4. Path system: NODEINFO_PATH с hop_count, routes → ROUTE_ENTRY → NODEINFO_Q → paths —
уже выбирается путь для маршрутизации пакетов.
5. Константа ROUTE_SUBCMD_CONN_REQ (0x0B) зарезервирована в route_bgp.h, но не реализована.
6. NODEINFO_Q уже имеет best_socket, last_ping_time, last_rtt.
=== Общий алгоритм (на примере узлов A и B) ===
A ──(ETCP через релейные узлы)──> B [текущий путь, hop_count > 1]
1. A замечает большой объём трафика к B → запускает CONN_REQ
2. A шлёт B через существующий ETCP-путь: свои адреса-кандидаты + лучших транзитных соседей
3. B получает CONN_REQ, сразу начинает пинговать адреса A, собирает свои данные
4. B шлёт CONN_RESP: свои адреса + транзитных соседей + результаты пробных пингов
5. A получает CONN_RESP, пингует все адреса B
6. Оба вычисляют лучший вариант (прямой или релейный с минимальным RTT)
7. Оба шлют друг другу CONN_RESULT с выбором
8. Оба пытаются создать ETCP link к выбранному адресу (если прямой)
9. Лишние/худшие линки закрываются позже — все линки в рамках одного ETCP_CONN
=== Фаза 1: Обмен кандидатами (CONN_REQ → CONN_RESP) ===
Узел-инициатор A собирает и отправляет:
- Свои адреса-кандидаты: из своих ETCP_SOCKET (interface_addr + nat_addr).
Включаются только сокеты с NAT_VERIFIED_* / EIM / PUBLIC (type >= NAT_VERIFIED_UNKNOWN).
Для каждого: ip, port, type, socket_id.
- Лучших транзитных соседей: top-N directly-connected узлов (hop_count == 1)
отсортированных по NODEINFO_Q.last_rtt (лучший RTT первым).
Узел B при получении CONN_REQ:
- Сохраняет кандидатов A
- Сразу запускает пробные пинги ко всем адресам A (etcp_send_ping_to_socket с pubkey)
- Собирает свои адреса-кандидаты
- Собирает своих транзитных соседей
- Шлёт CONN_RESP со своими данными + уже готовыми результатами проб
=== Фаза 2: Пробы (CONN_RESP → Пинги) ===
Узел A при получении CONN_RESP:
- Запускает пинги ко всем адресам B:
каждый адрес B пингуется с каждого своего сокета (N×M проб)
- Использует etcp_send_ping_to_socket с pubkey узла B (из NODEINFO)
- Пинг идёт напрямую по UDP (не через ETCP) — проверяет реальную достижимость
включая NAT traversal
- Ждёт все результаты либо таймаут
Параллельно B тоже завершает свои пробы (запущенные на шаге 3).
=== Фаза 3: Выбор лучшего пути (→ CONN_RESULT) ===
Каждая сторона независимо вычисляет:
best = {mode: none, rtt: 65535}
Прямые варианты:
for each (мой_сокет, peer_addr) где probe.ok:
if probe.rtt < best.rtt:
best = {direct, my_sock_idx, peer_addr_idx, probe.rtt}
Релейные варианты:
for each общий_транзитный_узел R (доступен и A и B):
// RTT(A↔R) берём из своих замеров (A знает RTT до своих соседей)
// RTT(R↔B) берём из транзитных соседей B (присланы в CONN_RESP)
total_rtt = A→R_rtt + R→B_rtt
if total_rtt < best.rtt:
best = {relay, R, total_rtt}
Обе стороны приходят к одинаковому выводу (информация симметрична).
Шлют CONN_RESULT с выбором.
Если прямой вариант — оба пытаются создать ETCP link к адресу пира.
Если релейный — используют существующий путь (уже работает).
=== Триггер запуска negotiation ===
В route_pkt (routing.c) при отправке пакета узлу с hop_count > 1:
- Накапливаем счётчик байт к этому узлу (per-node counter в bgp)
- При превышении порога (например 64KB) и если negotiation ещё не запущен — стартуем
- Cooldown: не чаще чем раз в 30 секунд для одной пары узлов
- Если negotiation уже в процессе для этой пары — не дублируем
=== Tranzit nodes — автоматическое заполнение ===
В route_bgp_update_my_nodeinfo (route_node.c) при каждом обновлении NODEINFO:
- Сканируем directly-connected соседей (из bgp->nodes, где hop_count == 1)
- Сортируем по NODEINFO_Q.last_rtt (лучший RTT первым)
- Берём top-N (до 4) и упаковываем в NODEINFO.tranzit_nodes как массив
NODEINFO_TRANZIT_NODE (node_id, rtt, link_q)
Плюс периодический refill (раз в ~10 сек) для свежести RTT замеров.
=== Структуры данных (новые, route_p2pconn.h) ===
#define P2P_MAX_ADDRS 8
#define P2P_MAX_RELAYS 8
#define P2P_MAX_PROBES (P2P_MAX_ADDRS * P2P_MAX_ADDRS) // до 64
#define P2P_PHASE_WAIT_RESP 0 // ждём CONN_RESP от пира
#define P2P_PHASE_PROBING 1 // пингуем адреса пира
#define P2P_PHASE_SELECTING 2 // выбор лучшего, отправка CONN_RESULT
#define P2P_PHASE_DONE 3 // завершено
// Один адрес-кандидат
struct P2P_ADDR {
uint32_t ip; // network byte order
uint16_t port;
uint8_t type; // NAT_VERIFIED_*
uint8_t socket_id;
};
// Один транзитный узел
struct P2P_RELAY {
uint64_t node_id;
uint16_t rtt; // x0.1ms
uint16_t link_q;
};
// Результат одного пробного пинга
struct P2P_PROBE_RESULT {
uint8_t my_sock_idx; // индекс в my_addrs[]
uint8_t peer_addr_idx; // индекс в peer_addrs[]
uint16_t rtt; // x0.1ms, 0 = fail
};
// Состояние одних переговоров (хранится в хеш-таблице bgp->p2p_negotiations)
struct P2P_NEGOTIATION {
struct ll_entry ll;
uint32_t request_id;
uint64_t peer_node_id;
uint8_t phase;
// Мои данные
struct P2P_ADDR my_addrs[P2P_MAX_ADDRS];
uint8_t my_addr_count;
struct P2P_RELAY my_relays[P2P_MAX_RELAYS];
uint8_t my_relay_count;
// Данные пира (заполняются из CONN_RESP)
struct P2P_ADDR peer_addrs[P2P_MAX_ADDRS];
uint8_t peer_addr_count;
struct P2P_RELAY peer_relays[P2P_MAX_RELAYS];
uint8_t peer_relay_count;
// Результаты проб
struct P2P_PROBE_RESULT probes[P2P_MAX_PROBES];
uint8_t probe_total; // сколько всего запланировано
uint8_t probe_done; // сколько завершилось (ok + fail)
// Лучший выбор
uint8_t best_mode; // 1=direct, 2=relay
uint64_t best_relay; // node_id релея (если mode=relay)
uint16_t best_rtt; // x0.1ms
uint8_t best_my_sock_idx; // индекс в my_addrs (если direct)
uint8_t best_peer_addr_idx;// индекс в peer_addrs (если direct)
void* timeout_timer; // общий таймаут на всю negotiation
};
// Счётчик трафика per-node (для триггера, хранится в bgp)
struct P2P_TRAFFIC_COUNTER {
uint64_t node_id;
uint64_t bytes_sent; // накоплено байт
uint64_t last_negotiation_time; // время последней попытки (0 = не было)
};
=== Протокольные пакеты (добавляются в route_bgp.h) ===
ROUTE_SUBCMD_CONN_REQ 0x0B // запрос прямого подключения (уже зарезервирован)
ROUTE_SUBCMD_CONN_RESP 0x0C // ответ с адресами + результаты проб
ROUTE_SUBCMD_CONN_RESULT 0x0E // финальный выбор
// Пакет CONN_REQ (A → B)
struct BGP_CONN_REQ {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_CONN_REQ
uint32_t request_id; // для корреляции
uint8_t addr_count; // число адресов-кандидатов
uint8_t relay_count; // число транзитных узлов
uint8_t reserved[2];
// далее динамически (размер = addr_count*8 + relay_count*12):
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
};
// Пакет CONN_RESP (B → A)
struct BGP_CONN_RESP {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESP
uint32_t request_id;
uint8_t addr_count; // адреса B
uint8_t relay_count; // транзитные узлы B
uint8_t probe_count; // готовые результаты проб B→A
uint8_t reserved;
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
// [probe_count × {peer_addr_idx[1] my_sock_idx[1] rtt[2] ok[1]}]
};
// Пакет CONN_RESULT (A ↔ B, финальный)
struct BGP_CONN_RESULT {
uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESULT
uint32_t request_id;
uint8_t chosen_mode; // 1=direct, 2=relay
uint8_t reserved;
uint16_t chosen_rtt; // x0.1ms
// Если direct:
uint32_t peer_ip; // IP пира к которому подключаемся
uint16_t peer_port;
uint8_t my_socket_id; // свой сокет для подключения
uint8_t peer_socket_id; // сокет пира
// Если relay (оверлей тех же байт):
// uint64_t relay_node_id;
};
=== API модуля route_p2pconn ===
// Запуск negotiation к узлу peer_node_id.
// Вызывается из route_pkt при превышении порога трафика.
int p2p_start_negotiation(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
// Проверка: запущена ли уже negotiation для этой пары
int p2p_is_negotiating(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
// Сбор своих адресов-кандидатов (из etcp_sockets)
int p2p_collect_my_addrs(struct ROUTE_BGP* bgp, struct P2P_ADDR* out, uint8_t max);
// Сбор лучших транзитных соседей (из directly-connected nodes)
int p2p_collect_my_relays(struct ROUTE_BGP* bgp, struct P2P_RELAY* out, uint8_t max);
// Обработчики входящих пакетов (вызываются из route_bgp_receive_cbk):
void p2p_handle_conn_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
void p2p_handle_conn_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
void p2p_handle_conn_result(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
// Отмена negotiation при удалении conn
void p2p_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
// Очистка всех negotiation (при destroy bgp)
void p2p_destroy_all(struct ROUTE_BGP* bgp);
=== Схема состояний negotiation ===
IDLE ──(трафик > порог)──→ WAIT_RESP ──(CONN_RESP получен)──→ PROBING
↑ ↑ │
│ (таймаут) (все пробы готовы)
│ ↓ │
└──────────────────────────── DONE ←──(CONN_RESULT отправлен)── SELECTING
=== Интеграция (какие файлы меняются) ===
Новые файлы:
src/route_p2pconn.h — структуры, константы, прототипы
src/route_p2pconn.c — вся логика negotiation
Изменения в существующих:
src/route_bgp.h — добавить ROUTE_SUBCMD_CONN_RESP 0x0C,
ROUTE_SUBCMD_CONN_RESULT 0x0E,
структуры пакетов BGP_CONN_REQ/RESP/RESULT
src/route_bgp.c — в route_bgp_receive_cbk добавить обработку
новых subcmd. В struct ROUTE_BGP добавить
поле p2p_negotiations (очередь/хеш negotiation)
и p2p_traffic_counters (per-node counters)
src/route_node.c — в route_bgp_update_my_nodeinfo заполнять
tranzit_nodes из RTT directly-connected соседей
src/routing.c — в route_pkt добавить накопление счётчика трафика
и вызов p2p_start_negotiation при превышении порога
src/Makefile.am — добавить route_p2pconn.c в сборку
=== Внутренняя логика route_p2pconn.c ===
p2p_start_negotiation(bgp, peer_node_id):
1. Проверить что нет активной negotiation для этой пары
2. Проверить cooldown (30 сек)
3. Создать struct P2P_NEGOTIATION, заполнить my_addrs, my_relays
4. Сформировать BGP_CONN_REQ и отправить через существующий путь к пиру
5. Поставить таймаут на всю negotiation (например 5 сек)
p2p_handle_conn_req(bgp, from_conn, data, len):
1. Распарсить BGP_CONN_REQ
2. Сохранить адреса и релеи инициатора в P2P_NEGOTIATION
3. Собрать свои my_addrs, my_relays
4. Запустить пробные пинги к адресам инициатора (p2p_start_probes)
5. Когда пробы готовы — отправить CONN_RESP с результатами
p2p_start_probes(neg):
1. Для каждой пары (мой_сокет, peer_addr) запланировать пинг
2. Вызвать etcp_send_ping_to_socket с pubkey пира
3. В коллбэке сохранить результат в probes[], инкрементировать probe_done
4. Когда probe_done == probe_total → вызвать p2p_select_best
p2p_select_best(neg):
1. Прямые: найти пару (my_sock, peer_addr) с минимальным rtt > 0
2. Релейные: для каждого общего транзитного узла посчитать суммарный rtt,
найти минимум
3. Сравнить прямой vs релейный лучший rtt
4. Записать выбор в neg->best_*
5. Отправить CONN_RESULT пиру
6. Вызвать p2p_establish_link(neg)
p2p_establish_link(neg):
1. Если best_mode == direct:
- Найти/создать ETCP_CONN к пиру (по node_id)
- Вызвать etcp_link_new с chosen address
2. Если best_mode == relay:
- Ничего не делаем, существующий путь уже работает
3. Пометить negotiation как DONE
+9 -1
View File
@@ -17,8 +17,16 @@ libuasync_a_SOURCES = \
socket_compat.h \
platform_compat.c \
platform_compat.h \
getmyip.c \
getmyip.h \
swm_min.c \
swm_min.h \
mem.c \
mem.h
mem.h \
radix.c \
radix.h \
serialize.c \
serialize.h
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
+60
View File
@@ -57,6 +57,49 @@ ip_str_t ip_to_str(const void *addr, int family) {
return result;
}
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr) {
ip_str_t result;
result.str[0] = '\0';
if (!addr) {
return result;
}
char ip_buf[INET6_ADDRSTRLEN];
ip_buf[0] = '\0';
uint16_t port = 0;
void *src = NULL;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
src = (void *)&ipv4->sin_addr;
port = ntohs(ipv4->sin_port);
}
else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
src = (void *)&ipv6->sin6_addr;
port = ntohs(ipv6->sin6_port);
}
else {
return result;
}
if (!inet_ntop(addr->ss_family, src, ip_buf, sizeof(ip_buf))) {
return result;
}
if (addr->ss_family == AF_INET6) {
// IPv6 обязательно в []
snprintf(result.str, sizeof(result.str), "[%s]:%u", ip_buf, port);
} else {
snprintf(result.str, sizeof(result.str), "%s:%u", ip_buf, port);
}
return result;
}
static const struct {
const char* name;
@@ -82,6 +125,8 @@ static const struct {
{"traffic", DEBUG_CATEGORY_TRAFFIC},
{"debug", DEBUG_CATEGORY_DEBUG},
{"general", DEBUG_CATEGORY_GENERAL},
{"nat", DEBUG_CATEGORY_NAT},
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -173,6 +218,8 @@ const char* debug_get_category_name(debug_category_t category_idx) {
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
case DEBUG_CATEGORY_NAT: return "NAT";
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
case DEBUG_CATEGORY_NONE: return "NONE";
default: return "UNKNOWN";
}
@@ -297,6 +344,19 @@ void debug_disable_file_output(void) {
}
}
/* Reopen log file (append mode) for SIGHUP. Complete operation with full error logging. */
int debug_reopen_log(void) {
if (!g_debug_config.output_file) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "No log path for reopen");
return -1;
}
const char* path = g_debug_config.output_file;
debug_disable_file_output();
debug_enable_file_output(path, 0); // append
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Log reopened: %s", path);
return 0;
}
/* Check if debug output should be shown for given level and category */
int debug_should_output(debug_level_t level, debug_category_t category_idx) {
if (category_idx < 0 || category_idx >= DEBUG_CATEGORY_COUNT) {
+16 -9
View File
@@ -6,9 +6,11 @@
#ifndef DEBUG_CONFIG_H
#define DEBUG_CONFIG_H
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "platform_compat.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h> // struct sockaddr_storage
#endif
@@ -55,7 +57,9 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_TRAFFIC 17 // Traffic flow (src/dst node IDs)
#define DEBUG_CATEGORY_DEBUG 18 // Current debugging
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
#define DEBUG_CATEGORY_COUNT 20 // Total number of categories
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_COUNT 22 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
@@ -103,16 +107,19 @@ void debug_set_output_file(const char* file_path);
/* Dual output configuration */
void debug_set_console_level(debug_level_t level);
void debug_set_file_level(debug_level_t level);
void debug_enable_file_output(const char* file_path, int truncate);
void debug_disable_file_output(void);
void debug_enable_console(int enable);
void debug_enable_file_output(const char* file_path, int truncate);
void debug_disable_file_output(void);
int debug_reopen_log(void);
void debug_enable_console(int enable);
// IP address to string (static buffer, single-threaded)
typedef struct {
char str[48]; // INET6_ADDRSTRLEN(45) + \0
char str[54]; // INET6_ADDRSTRLEN(45) + 6chars (:port) + \0
} ip_str_t;
ip_str_t ip_to_str(const void *addr, int family);
ip_str_t ip_to_str(const void *addr, int family);// big endian addr
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
// hex dump в лог
void log_dump(const char* prefix, const uint8_t* data, size_t len);
+102
View File
@@ -0,0 +1,102 @@
/**
* getmyip.c - Определение локального IP-адреса для исходящих соединений
*
* Использует временный UDP-сокет с connect() для определения
* локального IP, который ОС выберет для заданного маршрута.
*/
#include "getmyip.h"
#include "socket_compat.h"
#include "debug_config.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#endif
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
struct sockaddr_storage *result)
{
if (!local_addr || !remote_addr || !result) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument");
return -1;
}
int family = local_addr->ss_family;
if (family != AF_INET && family != AF_INET6) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: unsupported family %d", family);
return -1;
}
if (remote_addr->ss_family != family) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch");
return -1;
}
socket_t sock = socket_create_udp(family);
if (sock == SOCKET_INVALID) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed");
return -1;
}
// Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
struct sockaddr_storage bind_addr;
memcpy(&bind_addr, local_addr, sizeof(bind_addr));
if (family == AF_INET) {
((struct sockaddr_in *)&bind_addr)->sin_port = 0;
} else {
((struct sockaddr_in6 *)&bind_addr)->sin6_port = 0;
}
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (bind(sock, (struct sockaddr *)&bind_addr, bind_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
// Connect к remote_addr — ядро вычислит маршрут и выберет source IP
socklen_t remote_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (connect(sock, (struct sockaddr *)remote_addr, remote_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
// Получаем выбранный локальный адрес
struct sockaddr_storage local;
socklen_t local_len = sizeof(local);
if (getsockname(sock, (struct sockaddr *)&local, &local_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
socket_close_wrapper(sock);
// Копируем только IP (без порта)
memset(result, 0, sizeof(*result));
if (family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)result;
struct sockaddr_in *src = (struct sockaddr_in *)&local;
sin->sin_family = AF_INET;
sin->sin_addr = src->sin_addr;
sin->sin_port = 0;
} else {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)result;
struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&local;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = src6->sin6_addr;
sin6->sin6_port = 0;
}
return 0;
}
+45
View File
@@ -0,0 +1,45 @@
/**
* getmyip.h - Определение локального IP-адреса для исходящих соединений
*
* Определяет локальный IP, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*/
#ifndef GETMYIP_H
#define GETMYIP_H
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Определяет локальный IP-адрес, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*
* Создает временный UDP-сокет, выполняет bind на local_addr,
* connect к remote_addr и получает выбранный локальный IP через getsockname.
*
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения)
* @param remote_addr Удаленный адрес (для определения маршрута)
* @param result Заполняемый локальный IP (порт = 0)
* @return 0 при успехе, -1 при ошибке
*/
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
struct sockaddr_storage *result);
#ifdef __cplusplus
}
#endif
#endif
+33 -36
View File
@@ -51,7 +51,7 @@ static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name) {
if (!ua) return NULL;
struct ll_queue* q = u_calloc(1, sizeof(struct ll_queue));
@@ -61,6 +61,8 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
q->ua = ua;
q->size_limit = -1; // Без ограничения по умолчанию
q->hash_size = hash_size;
q->index_offset = index_offset;
q->index_size = index_size;
#ifdef QUEUE_THREAD_CHECK
#ifdef _WIN32
@@ -225,8 +227,8 @@ void queue_entry_free(struct ll_entry* entry) {
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return;
if (q->hash_size == 0 || entry->index_size == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, entry->index_size);
if (q->hash_size == 0 || q->index_size == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, q->index_size);
return;
}
@@ -291,10 +293,6 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
entry->index_offset = 0;
entry->index_size = 0;
entry->index_hash = 0;
if (q->hash_size > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
queue_dgram_free(entry);
@@ -330,23 +328,20 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
return 0;
}
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
#endif
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
}
entry->index_offset = index_offset;
entry->index_size = index_size;
entry->index_hash = make_hash(entry->data + index_offset, index_size);
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
@@ -382,18 +377,8 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
entry->index_offset = 0;
entry->index_size = 0;
entry->index_hash = 0;
if (q->hash_size > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
}
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
queue_entry_free(entry); // как было в оригинале
return -1;
@@ -419,22 +404,20 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
}
// С хешем для put_first
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
#endif
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
}
entry->index_offset = index_offset;
entry->index_size = index_size;
entry->index_hash = make_hash(entry->data + index_offset, index_size);
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
@@ -581,18 +564,32 @@ void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
// ==================== Поиск и удаление по ID ====================
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key, uint16_t index_size) {
if (!q || q->hash_size == 0 || !q->hash_table || index_size == 0 || !index_key) return NULL;
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key) {
if (!q || q->hash_size == 0 || !q->hash_table || q->index_size == 0 || !index_key) return NULL;
uint32_t hash_val = make_hash(index_key, index_size);
uint32_t hash_val = make_hash(index_key, q->index_size);
uint32_t slot = hash_val % q->hash_size;
struct ll_entry* entry = q->hash_table[slot];
while (entry) {
if (entry->index_size == index_size &&
(size_t)entry->index_offset + index_size <= (size_t)entry->size &&
memcmp(entry->data + entry->index_offset, index_key, index_size) == 0) {
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
return entry;
}
entry = entry->hash_next;
}
return NULL;
}
struct ll_entry* queue_find_next_by_index(struct ll_queue* q, const void* index_key,
struct ll_entry* prev_entry) {
if (!q || q->hash_size == 0 || !index_key || q->index_size == 0 || !prev_entry) return NULL;
struct ll_entry* entry = prev_entry->hash_next;
while (entry) {
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
return entry;
}
entry = entry->hash_next;
+26 -19
View File
@@ -63,8 +63,6 @@ struct ll_entry {
uint8_t* dgram; // Указатель на данные пакета
void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram
struct memory_pool* dgram_pool; // Пул для dgram (если используется)
uint16_t index_offset; // Смещение индекса в data[]
uint16_t index_size; // Длина индекса (0 = без индекса)
uint32_t index_hash; // Хеш по первым 4 байтам индекса
struct ll_entry* hash_next; // Следующий в хеш-цепочке
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc)
@@ -126,6 +124,8 @@ struct ll_queue {
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
uint16_t index_offset; // Смещение индекса в data[] (для всех entry очереди)
uint16_t index_size; // Размер индекса (0 = без индекса)
#ifdef QUEUE_THREAD_CHECK
#ifdef _WIN32
@@ -142,9 +142,12 @@ struct ll_queue {
* @brief Создаёт новую пустую очередь.
* @param ua экземпляр uasync (обязателен для отложенного resume коллбэков)
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
* @param index_offset смещение ключа индекса в data[] каждого entry (0 если hash_size=0)
* @param index_size размер ключа индекса (0 если hash_size=0)
* @param name имя очереди для отладки
* @return указатель на очередь или NULL при ошибке выделения памяти
*/
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name);
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name);
/**
* @brief Освобождает структуру очереди.
@@ -229,26 +232,20 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с явным указанием смещения индекса для hash.
* @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса (node_id) в data[]
* @param index_size размер индекса (8 для uint64_t node_id)
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди с явным указанием смещения индекса для hash.
* @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса в data[]
* @param index_size размер индекса
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
@@ -311,14 +308,24 @@ void queue_dgram_free(struct ll_entry* entry);
/* ==================== Поиск и удаление ==================== */
/**
* @brief Находит элемент по идентификатору (требуется hash_size > 0 при создании).
* @param q очередь
* @param id искомый идентификатор
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @return элемент или NULL
*/
struct ll_entry* queue_find_data_by_index(struct ll_queue* q,
const void* index_key,
uint16_t index_size);
const void* index_key);
/**
* @brief Находит следующий элемент с тем же ключом (продолжение поиска).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @param prev_entry предыдущий найденный элемент (от queue_find_data_by_index или предыдущего queue_find_next_by_index)
* @return следующий элемент с тем же ключом или NULL
*/
struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
const void* index_key,
struct ll_entry* prev_entry);
/**
* @brief Удаляет элемент из очереди (не освобождает память).
@@ -336,7 +343,7 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
* @return количество байт
*/
static inline size_t queue_total_bytes(struct ll_queue* q) {
return q ? q->total_bytes : 0;
return q->total_bytes;
}
#endif // LL_QUEUE_H
+70 -23
View File
@@ -20,7 +20,7 @@
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
#include <execinfo.h>
#include <pthread.h>
#define BACKTRACE_ENABLED 1
#define BACKTRACE_ENABLED 0
#elif defined(_WIN32)
#include <windows.h>
#include <dbghelp.h>
@@ -50,28 +50,31 @@ static void init_mutex() {
static void* allocated_head = NULL;
static size_t allocated_count = 0;
static size_t allocations_count = 0;
static size_t frees_count = 0;
static void hex_dump(const void* mem, size_t len) {
const uint8_t* data = (const uint8_t*)mem;
char line[128];
for (size_t i = 0; i < len; i += 16) {
printf("%08zx: ", i);
int off = snprintf(line, sizeof(line), "%08zx: ", i);
for (size_t j = 0; j < 16; j++) {
if (i + j < len) {
printf("%02x ", data[i + j]);
off += snprintf(line + off, sizeof(line) - off, "%02x ", data[i + j]);
} else {
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
}
}
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
for (size_t j = 0; j < 16; j++) {
if (i + j < len) {
uint8_t c = data[i + j];
printf("%c", (c >= 32 && c <= 126) ? c : '.');
off += snprintf(line + off, sizeof(line) - off, "%c", (c >= 32 && c <= 126) ? c : '.');
} else {
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
}
}
printf("\n");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s", line);
}
}
@@ -87,34 +90,30 @@ void u_check(void* ptr, const char* text, const char* location) {
bool error = false;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
if (prefix_start[i] != PADDING_FILL) {
printf("%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
error = true;
}
}
if (*pre_canary != CANARY) {
printf("%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *pre_canary);
error = true;
}
uint8_t* post_start = user + size + 4;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
if (post_start[i] != PADDING_FILL) {
printf("%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, post_start[i]);
error = true;
}
}
uint32_t* post_canary = (uint32_t*)(user + size);
if (*post_canary != CANARY) {
printf("%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *post_canary);
error = true;
}
if (error) {
printf("%s: %s: Allocated at: %s\n", location, text, alloc_loc);
printf("%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes\n", location, text, size, total);
printf("%s: %s: Full memory dump at base %p:\n", location, text, base);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Allocated at: %s", location, text, alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Full memory dump at base %p:", location, text, base);
hex_dump(base, total);
exit(EXIT_FAILURE);
}
@@ -141,6 +140,7 @@ void* u_malloc_impl(uint32_t size, const char* location) {
*next_ptr = allocated_head;
allocated_head = base;
allocated_count++;
allocations_count++;
UNLOCK();
return prefix_start + BOUNDARY_CHECK_SIZE + 8;
}
@@ -173,6 +173,8 @@ void u_free_impl(void* ptr, const char* location) {
if (!ptr) return;
u_check(ptr, "u_free", location);
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
char* alloc_loc = (char*)base;
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
// Remove from linked list (thread-safe)
LOCK();
void* prev = NULL;
@@ -186,17 +188,53 @@ void u_free_impl(void* ptr, const char* location) {
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
allocated_count--;
frees_count++;
found = true;
break;
}
prev = curr;
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
size_t active_count = allocated_count;
UNLOCK();
if (!found) {
printf("%s: u_free: Attempt to free unallocated or already freed pointer %p\n", location, ptr);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Free attempt at: %s", location);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Pointer: %p", ptr);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Base: %p", (void*)base);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Allocated at: %s", alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "User size: %u bytes", size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Active allocs: %zu", active_count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8));
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8);
if (size > 0 && size <= 1024 * 1024) {
uint32_t dump_size = (size > 256) ? 256 : size;
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
hex_dump(ptr, dump_size);
}
#if BACKTRACE_ENABLED
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Backtrace ---");
# if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
void* bt_buf[32];
int bt_size = backtrace(bt_buf, 32);
char** bt_symbols = backtrace_symbols(bt_buf, bt_size);
if (bt_symbols) {
for (int i = 0; i < bt_size; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %s", i, bt_symbols[i]);
free(bt_symbols);
}
# elif defined(_WIN32)
void* bt_buf[32];
USHORT frames = CaptureStackBackTrace(0, 32, bt_buf, NULL);
for (USHORT i = 0; i < frames; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %p", i, bt_buf[i]);
# endif
#endif
exit(EXIT_FAILURE);
}
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
memset(base, 0, total);
free(base);
}
@@ -212,12 +250,15 @@ void u_report_unfreed_blocks(void) {
LOCK();
size_t count = allocated_count;
void* head = allocated_head;
size_t alloc_count = allocations_count;
size_t free_count = frees_count;
UNLOCK();
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory stats: allocations=%zu, frees=%zu, current=%zu", alloc_count, free_count, count);
if (count == 0) {
printf("No unfreed memory blocks.\n");
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "No unfreed memory blocks.");
return;
}
printf("Unfreed memory blocks: %zu\n", count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Unfreed memory blocks: %zu, allocations: %zu frees: %zu", count, allocations_count, frees_count);
void* curr = head;
while (curr) {
char* alloc_loc = (char*)curr;
@@ -225,7 +266,13 @@ void u_report_unfreed_blocks(void) {
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4);
void* user_ptr = prefix_start + BOUNDARY_CHECK_SIZE + 8;
u_check(user_ptr, "unfreed_check", "u_report_unfreed_blocks");
printf(" - Allocated at: %s, size: %u bytes\n", alloc_loc, size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " - Allocated at: %s, size: %u bytes", alloc_loc, size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " Full memory dump at %p:", user_ptr);
hex_dump(user_ptr, size);
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
}
size_t u_get_allocated_count(void) {
return allocated_count;
}
+1
View File
@@ -24,6 +24,7 @@ void* u_realloc_impl(void* ptr, uint32_t size, const char* location);
void u_free_impl(void* ptr, const char* location);
char* u_strdup_impl(const char* s, const char* location);
void u_report_unfreed_blocks(void);
size_t u_get_allocated_count(void);
#ifdef __cplusplus
}
#endif
+22 -4
View File
@@ -5,6 +5,12 @@
#include "memory_pool.h"
#include "mem.h"
static size_t g_total_free = 0;
size_t memory_pool_get_total_free_blocks(void) {
return g_total_free;
}
// Инициализировать пул памяти
struct memory_pool* memory_pool_init(size_t object_size) {
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
@@ -20,7 +26,7 @@ struct memory_pool* memory_pool_init(size_t object_size) {
}
// Выделить объект из пула или из malloc
void* memory_pool_alloc(struct memory_pool* pool) {
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
if (!pool) {
return NULL;
}
@@ -29,17 +35,20 @@ void* memory_pool_alloc(struct memory_pool* pool) {
void* obj = pool->free_head;
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
pool->free_count--;
g_total_free--;
pool->reuse_count++;
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
return obj;
}
pool->allocations++;
return u_calloc(1, pool->object_size); // Используем calloc для инициализации нулями
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
return u_calloc_impl(1, pool->object_size, location); // Используем calloc для инициализации нулями
}
// Освободить объект в пул или в free
void memory_pool_free(struct memory_pool* pool, void* obj) {
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location) {
if (!pool || !obj) {
return;
}
@@ -49,11 +58,14 @@ void memory_pool_free(struct memory_pool* pool, void* obj) {
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
pool->free_head = obj;
pool->free_count++;
g_total_free++;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free returned: %s, free_count=%zu", location, pool->free_count);
return;
}
// Иначе освободить через free
u_free(obj);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
u_free_impl(obj, location);
}
// Получить статистику пула
@@ -75,14 +87,20 @@ void memory_pool_destroy(struct memory_pool* pool) {
return;
}
int cnt=0;
// Освободить все объекты в пуле
void* obj = pool->free_head;
while (obj) {
void* next = *(void**)obj;
g_total_free--;
u_free(obj);
cnt++;
obj = next;
}
// if (cnt>MEMORY_POOL_MAX_FREE)
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
pool->free_head = NULL;
pool->free_count = 0;
u_free(pool);
+12 -3
View File
@@ -3,8 +3,13 @@
#define MEMORY_POOL_H
#include <stddef.h>
#include "debug_config.h"
#define MEMORY_POOL_MAX_FREE 64 // Максимальное количество свободных объектов в пуле (чтобы не накапливать слишком много)
#define MEMORY_POOL_MAX_FREE 64
#define XSTR(s) STR(s)
#define STR(s) #s
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
struct memory_pool {
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
@@ -20,8 +25,12 @@ struct memory_pool* memory_pool_init(size_t object_size);
void memory_pool_destroy(struct memory_pool* pool);
// элементы пула:
void* memory_pool_alloc(struct memory_pool* pool);
void memory_pool_free(struct memory_pool* pool, void* obj);
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location);
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location);
void memory_pool_get_stats(struct memory_pool* pool, size_t* allocations, size_t* reuse_count);
size_t memory_pool_get_total_free_blocks(void);
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
#endif // MEMORY_POOL_H
+236
View File
@@ -0,0 +1,236 @@
/* outgoing_iface.c
*
* Определяет IPv4-адрес и индекс/имя исходящего интерфейса,
* который ОС выберет при отправке на заданный destination
* после bind на 0.0.0.0 (INADDR_ANY).
*
* Сборка:
* Linux / BSD: gcc -o out out.c
* Windows (MSVC): cl out.c /link ws2_32.lib iphlpapi.lib
* Windows (MinGW): gcc -o out.exe out.c -lws2_32 -liphlpapi
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ===================== Платформенные заголовки ===================== */
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <winsock2.h>
# include <ws2tcpip.h>
# include <iphlpapi.h>
# pragma comment(lib, "ws2_32.lib")
# pragma comment(lib, "iphlpapi.lib")
typedef SOCKET socket_t;
# define SOCKET_INVALID INVALID_SOCKET
# define SOCKET_ERRNO WSAGetLastError()
/* На Windows нет IFNAMSIZ; берём с запасом */
# ifndef IF_NAMESIZE
# define IF_NAMESIZE 256
# endif
#else /* Linux, FreeBSD, OpenBSD, NetBSD, macOS, … */
# include <unistd.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <ifaddrs.h>
# include <net/if.h>
# include <errno.h>
typedef int socket_t;
# define SOCKET_INVALID (-1)
# define SOCKET_ERRNO errno
# ifndef IF_NAMESIZE
# define IF_NAMESIZE IFNAMSIZ /* обычно 16 */
# endif
#endif
/* ===================== Структура результата ===================== */
typedef struct {
char ip[INET_ADDRSTRLEN]; /* IP строкой, например "192.168.1.5" */
char name[IF_NAMESIZE]; /* имя интерфейса: "eth0", "em0", "Wi-Fi" … */
uint32_t ifindex; /* числовой ifindex (if_nametoindex) */
} iface_info_t;
/* ===================== Утилиты ===================== */
static int net_init(void)
{
#ifdef _WIN32
static int done = 0;
if (!done) {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
done = 1;
}
#endif
return 0;
}
static void socket_close(socket_t s)
{
#ifdef _WIN32
if (s != SOCKET_INVALID)
closesocket(s);
#else
if (s >= 0)
close(s);
#endif
}
/* ===================== Основная функция ===================== */
/*
* dst_ip — адрес назначения (например, "8.8.8.8" или IP вашего VPN-сервера)
* dst_port — любой открытый порт (например, 53 или 123), реальных пакетов не уйдёт
* info — заполняемая структура
*
* Возвращает 0 при успехе, -1 при ошибке.
*/
int get_outgoing_iface(const char *dst_ip, int dst_port, iface_info_t *info)
{
socket_t sock = SOCKET_INVALID;
int ret = -1;
struct sockaddr_in dst, src;
socklen_t src_len = sizeof(src);
memset(info, 0, sizeof(*info));
if (net_init() != 0)
return -1;
/* 1. UDP-сокет */
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == SOCKET_INVALID)
return -1;
/* 2. Bind на 0.0.0.0 — именно так, как в вопросе */
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_addr.s_addr = INADDR_ANY; /* 0.0.0.0 */
dst.sin_port = 0; /* система выберет порт */
if (bind(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
goto end;
/* 3. "Соединяемся" с destination.
* Для UDP это НЕ шлёт реальных пакетов, но ядро просчитывает
* маршрут и фиксирует выбранный source IP в сокете. */
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_port = htons((uint16_t)dst_port);
if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1)
goto end;
if (connect(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
goto end;
/* 4. Забираем выбранный локальный адрес */
if (getsockname(sock, (struct sockaddr *)&src, &src_len) != 0)
goto end;
if (!inet_ntop(AF_INET, &src.sin_addr, info->ip, sizeof(info->ip)))
goto end;
/* 5. По source IP ищем интерфейс в системном списке */
#ifdef _WIN32
{
ULONG size = 0;
DWORD rv;
PIP_ADAPTER_ADDRESSES adapters = NULL, a;
PIP_ADAPTER_UNICAST_ADDRESS u;
/* первый вызов — узнать размер буфера */
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
NULL, NULL, &size);
if (rv != ERROR_BUFFER_OVERFLOW)
goto end;
adapters = (PIP_ADAPTER_ADDRESSES)malloc(size);
if (!adapters)
goto end;
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
NULL, adapters, &size);
if (rv != NO_ERROR) {
free(adapters);
goto end;
}
for (a = adapters; a != NULL; a = a->Next) {
for (u = a->FirstUnicastAddress; u; u = u->Next) {
struct sockaddr_in *ua =
(struct sockaddr_in *)u->Address.lpSockaddr;
if (ua->sin_addr.s_addr == src.sin_addr.s_addr) {
info->ifindex = (uint32_t)a->IfIndex;
/* FriendlyName — WCHAR, приводим к UTF-8 */
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1,
info->name, IF_NAMESIZE,
NULL, NULL);
ret = 0;
goto win_done;
}
}
}
win_done:
free(adapters);
}
#else /* Linux / BSD / macOS */
{
struct ifaddrs *ifap, *ifa;
if (getifaddrs(&ifap) != 0)
goto end;
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
continue;
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
if (addr->sin_addr.s_addr == src.sin_addr.s_addr) {
strncpy(info->name, ifa->ifa_name, IF_NAMESIZE - 1);
info->ifindex = if_nametoindex(ifa->ifa_name);
ret = 0;
break;
}
}
freeifaddrs(ifap);
}
#endif
end:
socket_close(sock);
return ret;
}
/* ===================== Пример использования =====================
int main(int argc, char **argv)
{
iface_info_t info;
const char *dst = (argc > 1) ? argv[1] : "8.8.8.8";
int port = (argc > 2) ? atoi(argv[2]) : 53;
if (get_outgoing_iface(dst, port, &info) == 0) {
printf("Destination : %s:%d\n", dst, port);
printf("Source IP : %s\n", info.ip);
printf("Iface name : %s\n", info.name);
printf("Iface index : %u\n", info.ifindex);
return 0;
}
fprintf(stderr, "Failed to determine outgoing interface (errno=%d)\n",
#ifdef _WIN32
(int)WSAGetLastError()
#else
errno
#endif
);
return 1;
}
*/
+127 -67
View File
@@ -24,6 +24,9 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#ifndef IFA_F_TEMPORARY
#define IFA_F_TEMPORARY 0x01
#endif
#endif
/*
@@ -48,49 +51,16 @@ int random_bytes(uint8_t *buffer, size_t len) {
}
#ifndef _WIN32
static uint32_t get_default_route_ip_linux(void) {
FILE *f = fopen("/proc/net/route", "r");
if (!f) return 0;
char line[256];
char *ifname = NULL;
unsigned int dest, gw;
if (!fgets(line, sizeof(line), f)) {
fclose(f);
return 0;
}
while (fgets(line, sizeof(line), f)) {
int items = sscanf(line, "%ms %x %x", &ifname, &dest, &gw);
if (items != 3) {
free(ifname);
continue;
}
if (dest == 0) {
free(ifname);
break;
}
free(ifname);
ifname = NULL;
}
fclose(f);
if (!ifname) return 0;
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
free(ifname);
return 0;
}
if (getifaddrs(&ifaddr) == -1) return 0;
uint32_t result = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
if (strcmp(ifa->ifa_name, ifname) != 0) continue;
unsigned int idx = if_nametoindex(ifa->ifa_name);
if (idx != netif_index) continue;
if (ifa->ifa_addr->sa_family != AF_INET) continue;
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
@@ -99,40 +69,130 @@ static uint32_t get_default_route_ip_linux(void) {
}
freeifaddrs(ifaddr);
free(ifname);
return result;
}
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
if (!addr_out) return -1;
memset(addr_out, 0, 16);
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) return -1;
int found = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
unsigned int idx = if_nametoindex(ifa->ifa_name);
if (idx != netif_index) continue;
if (ifa->ifa_addr->sa_family != AF_INET6) continue;
// Skip link-local addresses (fe80::/10)
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if ((sin6->sin6_addr.s6_addr[0] & 0xfe) == 0xfe && sin6->sin6_addr.s6_addr[1] == 0x80) continue;
int is_temporary = (ifa->ifa_flags & IFA_F_TEMPORARY) ? 1 : 0;
if (temporary && is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
}
if (!temporary && !is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
}
}
freeifaddrs(ifaddr);
return found ? 0 : -1;
}
uint32_t get_default_route_netif_index(int family) {
if (family != AF_INET && family != AF_INET6) return 0;
int sock = socket(family, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) return 0;
struct sockaddr_storage bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr));
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&bind_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&bind_addr;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
}
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (bind(sock, (struct sockaddr*)&bind_addr, bind_len) != 0) { close(sock); return 0; }
struct sockaddr_storage remote;
memset(&remote, 0, sizeof(remote));
socklen_t remote_len;
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&remote;
sin->sin_family = AF_INET;
sin->sin_port = htons(53);
inet_pton(AF_INET, "8.8.8.8", &sin->sin_addr);
remote_len = sizeof(struct sockaddr_in);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&remote;
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(53);
inet_pton(AF_INET6, "2001:4860:4860::8888", &sin6->sin6_addr);
remote_len = sizeof(struct sockaddr_in6);
}
if (connect(sock, (struct sockaddr*)&remote, remote_len) != 0) { close(sock); return 0; }
struct sockaddr_storage src;
socklen_t src_len = sizeof(src);
if (getsockname(sock, (struct sockaddr*)&src, &src_len) != 0) { close(sock); return 0; }
close(sock);
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) return 0;
uint32_t result = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_addr->sa_family != family) continue;
if (family == AF_INET) {
struct sockaddr_in* src_sin = (struct sockaddr_in*)&src;
struct sockaddr_in* ifa_sin = (struct sockaddr_in*)ifa->ifa_addr;
if (src_sin->sin_addr.s_addr == ifa_sin->sin_addr.s_addr) {
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
break;
}
} else {
struct sockaddr_in6* src_sin6 = (struct sockaddr_in6*)&src;
struct sockaddr_in6* ifa_sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if (memcmp(&src_sin6->sin6_addr, &ifa_sin6->sin6_addr, 16) == 0) {
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
break;
}
}
}
freeifaddrs(ifaddr);
return result;
}
#else
static uint32_t get_default_route_ip_windows(void) {
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) return 0;
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
(void)netif_index;
return 0;
}
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(53);
remote.sin_addr.s_addr = inet_addr("8.8.8.8");
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
(void)netif_index; (void)temporary;
if (addr_out) memset(addr_out, 0, 16);
return -1;
}
if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
struct sockaddr_in local;
int len = sizeof(local);
if (getsockname(s, (struct sockaddr *)&local, &len) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
closesocket(s);
return local.sin_addr.s_addr;
uint32_t get_default_route_netif_index(int family) {
(void)family;
return 0;
}
#endif
uint32_t get_default_route_ip(void) {
#ifdef _WIN32
return get_default_route_ip_windows();
#else
return get_default_route_ip_linux();
#endif
}
+17 -3
View File
@@ -17,9 +17,13 @@
// Byte order conversion functions
#ifdef _WIN32
#include <stdlib.h>
#define htobe16(x) _byteswap_ushort(x)
#define be16toh(x) _byteswap_ushort(x)
#define htobe32(x) _byteswap_ulong(x)
#define be32toh(x) _byteswap_ulong(x)
#define htobe64(x) _byteswap_uint64(x)
#define be64toh(x) _byteswap_uint64(x)
#elif defined(__linux__)
#else
#include <endian.h>
#endif
@@ -155,9 +159,19 @@
// Returns 0 on success, -1 on error
int random_bytes(uint8_t *buffer, size_t len);
// Get IPv4 address of interface with default route (0.0.0.0 destination)
// Get IPv4 address of interface by its index (ifindex)
// Returns IPv4 address in network byte order, 0 on error
uint32_t get_default_route_ip(void);
uint32_t get_interface_ip_by_index(uint32_t netif_index);
// Get IPv6 address of interface by its index (ifindex)
// temporary=1: get temporary (privacy) IPv6, temporary=0: get permanent IPv6
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out);
// Determine the interface index for the default route (outgoing traffic)
// Creates a temp UDP socket, connects to a well-known address to force
// kernel routing, then matches the chosen source IP to getifaddrs()
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
uint32_t get_default_route_netif_index(int family);
#endif // PLATFORM_COMPAT_H
+906
View File
@@ -0,0 +1,906 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1988, 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Routines to build and maintain radix trees for routing lookups.
*/
#include <string.h>
#include <assert.h>
#include "mem.h"
#include "debug_config.h"
#include "radix.h"
#define min(a, b) ((a) < (b) ? (a) : (b))
static struct radix_node
*rn_insert(void *, struct radix_head *, int *,
struct radix_node [2]),
*rn_newpair(void *, int, struct radix_node[2]),
*rn_search(const void *, struct radix_node *),
*rn_search_m(const void *, struct radix_node *, void *);
static struct radix_node *rn_addmask(const void *, struct radix_mask_head *, int,int);
static void rn_detachhead_internal(struct radix_head *);
#define RADIX_MAX_KEY_LEN 32
static uint8_t rn_zeros[RADIX_MAX_KEY_LEN];
static uint8_t rn_ones[RADIX_MAX_KEY_LEN] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
static int rn_lexobetter(const void *m_arg, const void *n_arg);
static struct radix_mask *
rn_new_radix_mask(struct radix_node *tt,
struct radix_mask *next);
static int rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf,
int skip);
/*
* The data structure for the keys is a radix tree with one way
* branching removed. The index rn_bit at an internal node n represents a bit
* position to be tested. The tree is arranged so that all descendants
* of a node n have keys whose bits all agree up to position rn_bit - 1.
* (We say the index of n is rn_bit.)
*
* There is at least one descendant which has a one bit at position rn_bit,
* and at least one with a zero there.
*
* A route is determined by a pair of key and mask. We require that the
* bit-wise logical and of the key and mask to be the key.
* We define the index of a route to associated with the mask to be
* the first bit number in the mask where 0 occurs (with bit number 0
* representing the highest order bit).
*
* We say a mask is normal if every bit is 0, past the index of the mask.
* If a node n has a descendant (k, m) with index(m) == index(n) == rn_bit,
* and m is a normal mask, then the route applies to every descendant of n.
* If the index(m) < rn_bit, this implies the trailing last few bits of k
* before bit b are all 0, (and hence consequently true of every descendant
* of n), so the route applies to all descendants of the node as well.
*
* Similar logic shows that a non-normal mask m such that
* index(m) <= index(n) could potentially apply to many children of n.
* Thus, for each non-host route, we attach its mask to a list at an internal
* node as high in the tree as we can go.
*
* The present version of the code makes use of normal routes in short-
* circuiting an explict mask and compare operation when testing whether
* a key satisfies a normal route, and also in remembering the unique leaf
* that governs a subtree.
*/
/*
* Most of the functions in this code assume that the key/mask arguments
* are sockaddr-like structures, where the first byte is a uint8_t
* indicating the size of the entire structure.
*
* To make the assumption more explicit, we use the LEN() macro to access
* this field. It is safe to pass an expression with side effects
* to LEN() as the argument is evaluated only once.
*/
#define LEN(x) ( (int) (*(const uint8_t *)(x)) )
static struct radix_node *
rn_search(const void *v_arg, struct radix_node *head)
{
struct radix_node *x;
const uint8_t *v;
for (x = head, v = v_arg; x->rn_bit >= 0;) {
if (x->rn_bmask & v[x->rn_offset])
x = x->rn_right;
else
x = x->rn_left;
}
return (x);
}
static struct radix_node *
rn_search_m(const void *v_arg, struct radix_node *head, void *m_arg)
{
struct radix_node *x;
const uint8_t *v = v_arg, *m = m_arg;
for (x = head; x->rn_bit >= 0;) {
if ((x->rn_bmask & m[x->rn_offset]) &&
(x->rn_bmask & v[x->rn_offset]))
x = x->rn_right;
else
x = x->rn_left;
}
return (x);
}
int
rn_refines(const void *m_arg, const void *n_arg)
{
const uint8_t *m = m_arg, *n = n_arg;
const uint8_t *lim, *lim2 = lim = n + LEN(n);
int longer = LEN(n++) - LEN(m++);
int masks_are_equal = 1;
if (longer > 0)
lim -= longer;
while (n < lim) {
if (*n & ~(*m))
return (0);
if (*n++ != *m++)
masks_are_equal = 0;
}
while (n < lim2)
if (*n++)
return (0);
if (masks_are_equal && (longer < 0))
for (lim2 = m - longer; m < lim2; )
if (*m++)
return (1);
return (!masks_are_equal);
}
struct radix_node *
rn_lookup(const void *v_arg, const void *m_arg, struct radix_head *head)
{
struct radix_node *x;
uint8_t *netmask;
if (m_arg != NULL) {
x = rn_addmask(m_arg, head->rnh_masks, 1,
head->rnh_treetop->rn_offset);
if (x == NULL)
return (NULL);
netmask = x->rn_key;
x = rn_match(v_arg, head);
while (x != NULL && x->rn_mask != netmask)
x = x->rn_dupedkey;
return (x);
}
if ((x = rn_match(v_arg, head)) == NULL)
return (NULL);
if (LEN(x->rn_key) != LEN(v_arg) || memcmp(x->rn_key, v_arg, LEN(v_arg)))
return (NULL);
if (x->rn_mask != NULL)
return (NULL);
return (x);
}
static int
rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf, int skip)
{
const uint8_t *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask;
const uint8_t *cplim;
int length = min(LEN(cp), LEN(cp2));
if (cp3 == NULL)
cp3 = rn_ones;
else
length = min(length, LEN(cp3));
cplim = cp + length; cp3 += skip; cp2 += skip;
for (cp += skip; cp < cplim; cp++, cp2++, cp3++)
if ((*cp ^ *cp2) & *cp3)
return (0);
return (1);
}
struct radix_node *
rn_match(const void *v_arg, struct radix_head *head)
{
const uint8_t *v = v_arg;
struct radix_node *t = head->rnh_treetop, *x;
const uint8_t *cp = v, *cp2;
const uint8_t *cplim;
struct radix_node *saved_t, *top = t;
int off = t->rn_offset, vlen = LEN(cp), matched_off;
int test, b, rn_bit;
for (; t->rn_bit >= 0; ) {
if (t->rn_bmask & cp[t->rn_offset])
t = t->rn_right;
else
t = t->rn_left;
}
if (t->rn_mask)
vlen = *t->rn_mask;
cp += off; cp2 = t->rn_key + off; cplim = v + vlen;
for (; cp < cplim; cp++, cp2++)
if (*cp != *cp2)
goto on1;
if (t->rn_flags & RNF_ROOT)
t = t->rn_dupedkey;
return (t);
on1:
test = (*cp ^ *cp2) & 0xff;
for (b = 7; (test >>= 1) > 0;)
b--;
matched_off = cp - v;
b += matched_off << 3;
rn_bit = -1 - b;
if ((saved_t = t)->rn_mask == 0)
t = t->rn_dupedkey;
for (; t; t = t->rn_dupedkey)
if (t->rn_flags & RNF_NORMAL) {
if (rn_bit <= t->rn_bit)
return (t);
} else if (rn_satisfies_leaf(v, t, matched_off))
return (t);
t = saved_t;
do {
struct radix_mask *m;
t = t->rn_parent;
m = t->rn_mklist;
while (m) {
if (m->rm_flags & RNF_NORMAL) {
if (rn_bit <= m->rm_bit)
return (m->rm_leaf);
} else {
off = min(t->rn_offset, matched_off);
x = rn_search_m(v, t, m->rm_mask);
while (x && x->rn_mask != m->rm_mask)
x = x->rn_dupedkey;
if (x && rn_satisfies_leaf(v, x, off))
return (x);
}
m = m->rm_mklist;
}
} while (t != top);
return (0);
}
struct radix_node *
rn_nextprefix(struct radix_node *rn)
{
for (rn = rn->rn_dupedkey; rn != NULL; rn = rn->rn_dupedkey) {
if (!(rn->rn_flags & RNF_ROOT))
return (rn);
}
return (NULL);
}
static struct radix_node *
rn_newpair(void *v, int b, struct radix_node nodes[2])
{
struct radix_node *tt = nodes, *t = tt + 1;
t->rn_bit = b;
t->rn_bmask = 0x80 >> (b & 7);
t->rn_left = tt;
t->rn_offset = b >> 3;
t->rn_parent = t->rn_right = NULL;
tt->rn_bit = -1;
tt->rn_mask = NULL;
tt->rn_dupedkey = NULL;
tt->rn_bmask = 0;
tt->rn_key = (uint8_t *)v;
tt->rn_parent = t;
tt->rn_flags = t->rn_flags = RNF_ACTIVE;
tt->rn_mklist = t->rn_mklist = 0;
return (t);
}
static struct radix_node *
rn_insert(void *v_arg, struct radix_head *head, int *dupentry,
struct radix_node nodes[2])
{
uint8_t *v = v_arg;
struct radix_node *top = head->rnh_treetop;
int head_off = top->rn_offset, vlen = LEN(v);
struct radix_node *t = rn_search(v_arg, top);
uint8_t *cp = v + head_off;
unsigned b;
struct radix_node *p, *tt, *x;
uint8_t *cp2 = t->rn_key + head_off;
int cmp_res;
uint8_t *cplim = v + vlen;
while (cp < cplim)
if (*cp2++ != *cp++)
goto on1;
*dupentry = 1;
return (t);
on1:
*dupentry = 0;
cmp_res = (cp[-1] ^ cp2[-1]) & 0xff;
for (b = (cp - v) << 3; cmp_res; b--)
cmp_res >>= 1;
x = top;
cp = v;
do {
p = x;
if (cp[x->rn_offset] & x->rn_bmask)
x = x->rn_right;
else
x = x->rn_left;
} while (b > (unsigned) x->rn_bit);
t = rn_newpair(v_arg, b, nodes);
tt = t->rn_left;
if ((cp[p->rn_offset] & p->rn_bmask) == 0)
p->rn_left = t;
else
p->rn_right = t;
x->rn_parent = t;
t->rn_parent = p;
if ((cp[t->rn_offset] & t->rn_bmask) == 0) {
t->rn_right = x;
} else {
t->rn_right = tt;
t->rn_left = x;
}
return (tt);
}
static struct radix_node *
rn_addmask(const void *n_arg, struct radix_mask_head *maskhead, int search, int skip)
{
const uint8_t *netmask = n_arg;
const uint8_t *c, *clim;
uint8_t *cp;
struct radix_node *x;
int b = 0, mlen, j;
int maskduplicated, isnormal;
struct radix_node *saved_x;
uint8_t addmask_key[RADIX_MAX_KEY_LEN];
if ((mlen = LEN(netmask)) > RADIX_MAX_KEY_LEN)
mlen = RADIX_MAX_KEY_LEN;
if (skip == 0)
skip = 1;
if (mlen <= skip)
return (maskhead->mask_nodes);
memset(addmask_key, 0, RADIX_MAX_KEY_LEN);
if (skip > 1)
memcpy(addmask_key + 1, rn_ones + 1, skip - 1);
memcpy(addmask_key + skip, netmask + skip, mlen - skip);
for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;)
cp--;
mlen = cp - addmask_key;
if (mlen <= skip)
return (maskhead->mask_nodes);
*addmask_key = mlen;
x = rn_search(addmask_key, maskhead->head.rnh_treetop);
if (memcmp(addmask_key, x->rn_key, mlen) != 0)
x = NULL;
if (x || search)
return (x);
R_Zalloc(x, struct radix_node *, RADIX_MAX_KEY_LEN + 2 * sizeof (*x));
if ((saved_x = x) == NULL)
return (0);
netmask = cp = (uint8_t *)(x + 2);
memcpy(cp, addmask_key, mlen);
x = rn_insert(cp, &maskhead->head, &maskduplicated, x);
if (maskduplicated) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_addmask: mask impossibly already in tree");
R_Free(saved_x);
return (x);
}
#define CONTIG(_c) (((~(_c) + 1) & (_c)) == (uint8_t)(~(_c) + 1))
clim = netmask + mlen;
isnormal = 1;
for (c = netmask + skip; c < clim && *c == 0xff; c++);
if (c != clim) {
for (j = 0x80; (j & *c) != 0; j >>= 1)
b++;
if (!CONTIG(*c) || c != (clim - 1))
isnormal = 0;
}
b += (c - netmask) << 3;
x->rn_bit = -1 - b;
if (isnormal)
x->rn_flags |= RNF_NORMAL;
return (x);
}
static int
rn_lexobetter(const void *m_arg, const void *n_arg)
{
const uint8_t *mp = m_arg, *np = n_arg, *lim;
if (LEN(mp) > LEN(np))
return (1);
if (LEN(mp) == LEN(np))
for (lim = mp + LEN(mp); mp < lim;)
if (*mp++ > *np++)
return (1);
return (0);
}
static struct radix_mask *
rn_new_radix_mask(struct radix_node *tt, struct radix_mask *next)
{
struct radix_mask *m;
R_Malloc(m, struct radix_mask *, sizeof (struct radix_mask));
if (m == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to allocate route mask");
return (0);
}
memset(m, 0, sizeof(*m));
m->rm_bit = tt->rn_bit;
m->rm_flags = tt->rn_flags;
if (tt->rn_flags & RNF_NORMAL)
m->rm_leaf = tt;
else
m->rm_mask = tt->rn_mask;
m->rm_mklist = next;
tt->rn_mklist = m;
return (m);
}
struct radix_node *
rn_addroute(void *v_arg, const void *n_arg, struct radix_head *head,
struct radix_node treenodes[2])
{
uint8_t *v = (uint8_t *)v_arg, *netmask = NULL;
struct radix_node *t, *x = NULL, *tt;
struct radix_node *saved_tt, *top = head->rnh_treetop;
short b = 0, b_leaf = 0;
int keyduplicated;
uint8_t *mmask;
struct radix_mask *m, **mp;
if (n_arg) {
x = rn_addmask(n_arg, head->rnh_masks, 0, top->rn_offset);
if (x == NULL)
return (0);
b_leaf = x->rn_bit;
b = -1 - x->rn_bit;
netmask = x->rn_key;
}
saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes);
if (keyduplicated) {
for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) {
if (tt->rn_mask == netmask)
return (0);
if (netmask == 0 ||
(tt->rn_mask &&
((b_leaf < tt->rn_bit)
|| rn_refines(netmask, tt->rn_mask)
|| rn_lexobetter(netmask, tt->rn_mask))))
break;
}
if (tt == saved_tt) {
struct radix_node *xx = x;
(tt = treenodes)->rn_dupedkey = t;
tt->rn_flags = t->rn_flags;
tt->rn_parent = x = t->rn_parent;
t->rn_parent = tt;
if (x->rn_left == t)
x->rn_left = tt;
else
x->rn_right = tt;
saved_tt = tt; x = xx;
} else {
(tt = treenodes)->rn_dupedkey = t->rn_dupedkey;
t->rn_dupedkey = tt;
tt->rn_parent = t;
if (tt->rn_dupedkey)
tt->rn_dupedkey->rn_parent = tt;
}
tt->rn_key = (uint8_t *) v;
tt->rn_bit = -1;
tt->rn_flags = RNF_ACTIVE;
}
if (netmask) {
tt->rn_mask = netmask;
tt->rn_bit = x->rn_bit;
tt->rn_flags |= x->rn_flags & RNF_NORMAL;
}
t = saved_tt->rn_parent;
if (keyduplicated)
goto on2;
b_leaf = -1 - t->rn_bit;
if (t->rn_right == saved_tt)
x = t->rn_left;
else
x = t->rn_right;
if (x->rn_bit < 0) {
for (mp = &t->rn_mklist; x; x = x->rn_dupedkey)
if (x->rn_mask && (x->rn_bit >= b_leaf) && x->rn_mklist == 0) {
*mp = m = rn_new_radix_mask(x, 0);
if (m)
mp = &m->rm_mklist;
}
} else if (x->rn_mklist) {
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
if (m->rm_bit >= b_leaf)
break;
t->rn_mklist = m; *mp = NULL;
}
on2:
if ((netmask == 0) || (b > t->rn_bit ))
return (tt);
b_leaf = tt->rn_bit;
do {
x = t;
t = t->rn_parent;
} while (b <= t->rn_bit && x != top);
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) {
if (m->rm_bit < b_leaf)
continue;
if (m->rm_bit > b_leaf)
break;
if (m->rm_flags & RNF_NORMAL) {
mmask = m->rm_leaf->rn_mask;
if (tt->rn_flags & RNF_NORMAL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
"Non-unique normal route, mask not entered");
return (tt);
}
} else
mmask = m->rm_mask;
if (mmask == netmask) {
m->rm_refs++;
tt->rn_mklist = m;
return (tt);
}
if (rn_refines(netmask, mmask)
|| rn_lexobetter(netmask, mmask))
break;
}
*mp = rn_new_radix_mask(tt, *mp);
return (tt);
}
struct radix_node *
rn_delete(const void *v_arg, const void *netmask_arg, struct radix_head *head)
{
struct radix_node *t, *p, *x, *tt;
struct radix_mask *m, *saved_m, **mp;
struct radix_node *dupedkey, *saved_tt, *top;
const uint8_t *v;
const uint8_t *netmask;
int b, head_off, vlen;
v = v_arg;
netmask = netmask_arg;
x = head->rnh_treetop;
tt = rn_search(v, x);
head_off = x->rn_offset;
vlen = LEN(v);
saved_tt = tt;
top = x;
if (tt == NULL ||
memcmp(v + head_off, tt->rn_key + head_off, vlen - head_off))
return (0);
if (netmask) {
x = rn_addmask(netmask, head->rnh_masks, 1, head_off);
if (x == NULL)
return (0);
netmask = x->rn_key;
while (tt->rn_mask != netmask)
if ((tt = tt->rn_dupedkey) == NULL)
return (0);
}
if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == NULL)
goto on1;
if (tt->rn_flags & RNF_NORMAL) {
if (m->rm_leaf != tt || m->rm_refs > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
return (0);
}
} else {
if (m->rm_mask != tt->rn_mask) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
goto on1;
}
if (--m->rm_refs >= 0)
goto on1;
}
b = -1 - tt->rn_bit;
t = saved_tt->rn_parent;
if (b > t->rn_bit)
goto on1;
do {
x = t;
t = t->rn_parent;
} while (b <= t->rn_bit && x != top);
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
if (m == saved_m) {
*mp = m->rm_mklist;
R_Free(m);
break;
}
if (m == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find our annotation");
if (tt->rn_flags & RNF_NORMAL)
return (0);
}
on1:
if (tt->rn_flags & RNF_ROOT)
return (0);
t = tt->rn_parent;
dupedkey = saved_tt->rn_dupedkey;
if (dupedkey) {
if (tt == saved_tt) {
x = dupedkey; x->rn_parent = t;
if (t->rn_left == tt)
t->rn_left = x;
else
t->rn_right = x;
} else {
for (x = p = saved_tt; p && p->rn_dupedkey != tt;)
p = p->rn_dupedkey;
if (p) {
p->rn_dupedkey = tt->rn_dupedkey;
if (tt->rn_dupedkey)
tt->rn_dupedkey->rn_parent = p;
} else DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find us");
}
t = tt + 1;
if (t->rn_flags & RNF_ACTIVE) {
*++x = *t;
p = t->rn_parent;
if (p->rn_left == t)
p->rn_left = x;
else
p->rn_right = x;
x->rn_left->rn_parent = x;
x->rn_right->rn_parent = x;
}
goto out;
}
if (t->rn_left == tt)
x = t->rn_right;
else
x = t->rn_left;
p = t->rn_parent;
if (p->rn_right == t)
p->rn_right = x;
else
p->rn_left = x;
x->rn_parent = p;
if (t->rn_mklist) {
if (x->rn_bit >= 0) {
for (mp = &x->rn_mklist; (m = *mp);)
mp = &m->rm_mklist;
*mp = t->rn_mklist;
} else {
for (m = t->rn_mklist; m && x; x = x->rn_dupedkey)
if (m == x->rn_mklist) {
struct radix_mask *mm = m->rm_mklist;
x->rn_mklist = 0;
if (--(m->rm_refs) < 0)
R_Free(m);
m = mm;
}
if (m)
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
"rn_delete: Orphaned Mask %p at %p", m, x);
}
}
x = tt + 1;
if (t != x) {
*t = *x;
t->rn_left->rn_parent = t;
t->rn_right->rn_parent = t;
p = x->rn_parent;
if (p->rn_left == x)
p->rn_left = t;
else
p->rn_right = t;
}
out:
tt->rn_flags &= ~RNF_ACTIVE;
tt[1].rn_flags &= ~RNF_ACTIVE;
return (tt);
}
int
rn_walktree_from(struct radix_head *h, void *a, void *m,
walktree_f_t *f, void *w)
{
int error;
struct radix_node *base, *next;
uint8_t *xa = (uint8_t *)a;
uint8_t *xm = (uint8_t *)m;
struct radix_node *rn, *last = NULL;
int stopping = 0;
int lastb;
assert(m != NULL);
for (rn = h->rnh_treetop; rn->rn_bit >= 0; ) {
last = rn;
if (!(rn->rn_bmask & xm[rn->rn_offset]))
break;
if (rn->rn_bmask & xa[rn->rn_offset])
rn = rn->rn_right;
else
rn = rn->rn_left;
}
if (rn->rn_bit >= 0)
rn = last;
lastb = last->rn_bit;
while (rn->rn_bit >= 0)
rn = rn->rn_left;
while (!stopping) {
base = rn;
while (rn->rn_parent->rn_right == rn
&& !(rn->rn_flags & RNF_ROOT)) {
rn = rn->rn_parent;
if (rn->rn_bit <= lastb)
stopping = 1;
}
if (rn->rn_parent->rn_flags & RNF_ROOT)
stopping = 1;
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
rn = rn->rn_left;
next = rn;
while ((rn = base) != NULL) {
base = rn->rn_dupedkey;
if (!(rn->rn_flags & RNF_ROOT)
&& (error = (*f)(rn, w)))
return (error);
}
rn = next;
if (rn->rn_flags & RNF_ROOT)
stopping = 1;
}
return (0);
}
int
rn_walktree(struct radix_head *h, walktree_f_t *f, void *w)
{
int error;
struct radix_node *base, *next;
struct radix_node *rn = h->rnh_treetop;
while (rn->rn_bit >= 0)
rn = rn->rn_left;
for (;;) {
base = rn;
while (rn->rn_parent->rn_right == rn
&& (rn->rn_flags & RNF_ROOT) == 0)
rn = rn->rn_parent;
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
rn = rn->rn_left;
next = rn;
while ((rn = base)) {
base = rn->rn_dupedkey;
if (!(rn->rn_flags & RNF_ROOT)
&& (error = (*f)(rn, w)))
return (error);
}
rn = next;
if (rn->rn_flags & RNF_ROOT)
return (0);
}
}
void
rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes, int off)
{
struct radix_node *t, *tt, *ttt;
t = rn_newpair(rn_zeros, off, base_nodes);
ttt = base_nodes + 2;
t->rn_right = ttt;
t->rn_parent = t;
tt = t->rn_left;
tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE;
tt->rn_bit = -1 - off;
*ttt = *tt;
ttt->rn_key = rn_ones;
rh->rnh_treetop = t;
}
static void
rn_detachhead_internal(struct radix_head *head)
{
assert(head != NULL);
R_Free(head);
}
int
rn_inithead(void **head, int off)
{
struct radix_node_head *rnh;
struct radix_mask_head *rmh;
rnh = *head;
rmh = NULL;
if (*head != NULL)
return (1);
R_Zalloc(rnh, struct radix_node_head *, sizeof (*rnh));
R_Zalloc(rmh, struct radix_mask_head *, sizeof (*rmh));
if (rnh == NULL || rmh == NULL) {
if (rnh != NULL)
R_Free(rnh);
if (rmh != NULL)
R_Free(rmh);
return (0);
}
rn_inithead_internal(&rnh->rh, rnh->rnh_nodes, off);
rn_inithead_internal(&rmh->head, rmh->mask_nodes, 0);
*head = rnh;
rnh->rh.rnh_masks = rmh;
rnh->rnh_addaddr = rn_addroute;
rnh->rnh_deladdr = rn_delete;
rnh->rnh_matchaddr = rn_match;
rnh->rnh_lookup = rn_lookup;
rnh->rnh_walktree = rn_walktree;
rnh->rnh_walktree_from = rn_walktree_from;
return (1);
}
static int
rn_freeentry(struct radix_node *rn, void *arg)
{
struct radix_head * const rnh = arg;
struct radix_node *x;
x = (struct radix_node *)rn_delete(rn + 2, NULL, rnh);
if (x != NULL)
R_Free(x);
return (0);
}
int
rn_detachhead(void **head)
{
struct radix_node_head *rnh;
assert(head != NULL && *head != NULL);
rnh = (struct radix_node_head *)(*head);
rn_walktree(&rnh->rh.rnh_masks->head, rn_freeentry, rnh->rh.rnh_masks);
rn_detachhead_internal(&rnh->rh.rnh_masks->head);
rn_detachhead_internal(&rnh->rh);
*head = NULL;
return (1);
}
+164
View File
@@ -0,0 +1,164 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1988, 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _RADIX_H_
#define _RADIX_H_
#include <stdint.h>
#include "mem.h"
/*
* Radix search tree node layout.
*/
struct radix_node {
struct radix_mask *rn_mklist; /* list of masks contained in subtree */
struct radix_node *rn_parent; /* parent */
short rn_bit; /* bit offset; -1-index(netmask) */
char rn_bmask; /* node: mask for bit test*/
uint8_t rn_flags; /* enumerated next */
#define RNF_NORMAL 1 /* leaf contains normal route */
#define RNF_ROOT 2 /* leaf is root leaf for tree */
#define RNF_ACTIVE 4 /* This node is alive (for rtfree) */
union {
struct { /* leaf only data: */
uint8_t *rn_Key; /* object of search */
uint8_t *rn_Mask; /* netmask, if present */
struct radix_node *rn_Dupedkey;
} rn_leaf;
struct { /* node only data: */
int rn_Off; /* where to start compare */
struct radix_node *rn_L;/* progeny */
struct radix_node *rn_R;/* progeny */
} rn_node;
} rn_u;
};
#define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey
#define rn_key rn_u.rn_leaf.rn_Key
#define rn_mask rn_u.rn_leaf.rn_Mask
#define rn_offset rn_u.rn_node.rn_Off
#define rn_left rn_u.rn_node.rn_L
#define rn_right rn_u.rn_node.rn_R
/*
* Annotations to tree concerning potential routes applying to subtrees.
*/
struct radix_mask {
short rm_bit; /* bit offset; -1-index(netmask) */
char rm_unused; /* cf. rn_bmask */
uint8_t rm_flags; /* cf. rn_flags */
struct radix_mask *rm_mklist; /* more masks to try */
union {
uint8_t *rmu_mask; /* the mask */
struct radix_node *rmu_leaf; /* for normal routes */
} rm_rmu;
int rm_refs; /* # of references to this struct */
};
#define rm_mask rm_rmu.rmu_mask
#define rm_leaf rm_rmu.rmu_leaf /* extra field would make 32 bytes */
struct radix_head;
typedef int walktree_f_t(struct radix_node *, void *);
typedef struct radix_node *rn_matchaddr_f_t(const void *v,
struct radix_head *head);
typedef struct radix_node *rn_addaddr_f_t(void *v, const void *mask,
struct radix_head *head, struct radix_node nodes[]);
typedef struct radix_node *rn_deladdr_f_t(const void *v, const void *mask,
struct radix_head *head);
typedef struct radix_node *rn_lookup_f_t(const void *v, const void *mask,
struct radix_head *head);
typedef int rn_walktree_t(struct radix_head *head, walktree_f_t *f,
void *w);
typedef int rn_walktree_from_t(struct radix_head *head,
void *a, void *m, walktree_f_t *f, void *w);
typedef void rn_close_t(struct radix_node *rn, struct radix_head *head);
struct radix_node *rn_nextprefix(struct radix_node *rn);
struct radix_mask_head;
struct radix_head {
struct radix_node *rnh_treetop;
struct radix_mask_head *rnh_masks; /* Storage for our masks */
};
struct radix_node_head {
struct radix_head rh;
rn_matchaddr_f_t *rnh_matchaddr; /* longest match for sockaddr */
rn_addaddr_f_t *rnh_addaddr; /* add based on sockaddr*/
rn_deladdr_f_t *rnh_deladdr; /* remove based on sockaddr */
rn_lookup_f_t *rnh_lookup; /* exact match for sockaddr */
rn_walktree_t *rnh_walktree; /* traverse tree */
rn_walktree_from_t *rnh_walktree_from; /* traverse tree below a */
rn_close_t *rnh_close; /*do something when the last ref drops*/
struct radix_node rnh_nodes[3]; /* empty tree for common case */
};
struct radix_mask_head {
struct radix_head head;
struct radix_node mask_nodes[3];
};
void rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes,
int off);
#define R_Malloc(p, t, n) (p = (t) u_malloc((unsigned int)(n)))
#define R_Zalloc(p, t, n) (p = (t) u_calloc(1, (unsigned int)(n)))
#define R_Free(p) u_free(p)
/* Stub lock macros — single-threaded with u_async */
#define RADIX_NODE_HEAD_RLOCK_TRACKER
#define RADIX_NODE_HEAD_LOCK_INIT(rnh)
#define RADIX_NODE_HEAD_LOCK(rnh)
#define RADIX_NODE_HEAD_UNLOCK(rnh)
#define RADIX_NODE_HEAD_RLOCK(rnh)
#define RADIX_NODE_HEAD_RUNLOCK(rnh)
#define RADIX_NODE_HEAD_DESTROY(rnh)
#define RADIX_NODE_HEAD_LOCK_ASSERT(rnh)
#define RADIX_NODE_HEAD_WLOCK_ASSERT(rnh)
int rn_inithead(void **, int);
int rn_detachhead(void **);
int rn_refines(const void *, const void *);
struct radix_node *rn_addroute(void *, const void *, struct radix_head *,
struct radix_node[2]);
struct radix_node *rn_delete(const void *, const void *, struct radix_head *);
struct radix_node *rn_lookup (const void *v_arg, const void *m_arg,
struct radix_head *head);
struct radix_node *rn_match(const void *, struct radix_head *);
int rn_walktree_from(struct radix_head *h, void *a, void *m,
walktree_f_t *f, void *w);
int rn_walktree(struct radix_head *, walktree_f_t *, void *);
#endif /* _RADIX_H_ */
+348
View File
@@ -0,0 +1,348 @@
// serialize.c - Universal binary serialization with dynamic allocation
#include "serialize.h"
#include <string.h>
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size схемы
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
static uint16_t length_encode(uint8_t* out, uint16_t val) {
if (out) {
out[0] = (uint8_t)(val >> 8);
out[1] = (uint8_t)val;
}
return 2;
}
static uint16_t length_decode(const uint8_t* in, size_t in_len, size_t* consumed) {
if (!in || in_len < 2) {
if (consumed) *consumed = 0;
return 0;
}
if (consumed) *consumed = 2;
return ((uint16_t)in[0] << 8) | in[1];
}
static uint32_t get_var_count(const uint8_t* data, const struct SerializeField* f) {
if (!data || !f) return 0;
const uint8_t* len_ptr = data + f->len_offset;
if (f->type == SERIALIZE_TYPE_ARRAY_U8) return *len_ptr;
if (f->type == SERIALIZE_TYPE_ARRAY_U16) return *(const uint16_t*)len_ptr;
if (f->type == SERIALIZE_TYPE_ARRAY_U32) return *(const uint32_t*)len_ptr;
return 0;
}
static void set_var_count(uint8_t* data, const struct SerializeField* f, uint16_t count) {
if (!data || !f) return;
uint8_t* len_ptr = data + f->len_offset;
if (f->type == SERIALIZE_TYPE_ARRAY_U8) *len_ptr = (uint8_t)count;
else if (f->type == SERIALIZE_TYPE_ARRAY_U16) *(uint16_t*)len_ptr = count;
else if (f->type == SERIALIZE_TYPE_ARRAY_U32) *(uint32_t*)len_ptr = count;
}
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len) {
if (!schema || !structure || !out_buf || !out_len) return SERIALIZE_ERR_NULL;
const uint8_t* data = (const uint8_t*)structure;
size_t total = schema->header_len;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_FIXED || f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
total += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint32_t count = 0;
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
const char* str = *(const char**)(data + f->offset);
count = str ? (uint32_t)strlen(str) : 0;
} else {
count = get_var_count(data, f);
}
uint16_t len = (count > 0xFFFFu) ? 0xFFFFu : (uint16_t)count;
total += 2;
if (f->elem_size == 1) {
total += (size_t)len + 1; // данные + '\0'
} else {
total += (size_t)len * f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
// подсчёт узлов (максимум 65535)
void* cur = *(void**)(data + f->offset);
uint16_t count = 0;
while (cur && count < 0xFFFFu) {
count++;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
total += 2 + (size_t)count * f->elem_size;
} else {
return SERIALIZE_ERR_NOTSUP;
}
}
if (schema->max_size > 0 && total > schema->max_size) {
return SERIALIZE_ERR_SIZE;
}
uint8_t* buf = u_malloc(total);
if (!buf) return SERIALIZE_ERR_BUF;
uint8_t* ptr = buf;
if (header && schema->header_len > 0) {
memcpy(ptr, header, schema->header_len);
ptr += schema->header_len;
}
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
const uint8_t* fld = data + f->offset;
if (f->type == SERIALIZE_TYPE_FIXED) {
if (f->elem_size > 0) {
memcpy(ptr, fld, f->elem_size);
ptr += f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
const uint8_t* arr = *(const uint8_t**)fld;
if (f->elem_size > 0) {
if (arr) memcpy(ptr, arr, f->elem_size);
else memset(ptr, 0, f->elem_size);
}
ptr += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint32_t raw_count = 0;
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
const char* str = *(const char**)fld;
raw_count = str ? (uint32_t)strlen(str) : 0;
} else {
raw_count = get_var_count(data, f);
}
uint16_t len = (raw_count > 0xFFFFu) ? 0xFFFFu : (uint16_t)raw_count;
if (len > 0 && f->type == SERIALIZE_TYPE_ASCIIZ) {
if (!*(const char**)fld) len = 0;
} else if (len > 0 && f->type != SERIALIZE_TYPE_ASCIIZ) {
if (!*(const uint8_t**)fld) len = 0;
}
ptr += length_encode(ptr, len);
if (f->elem_size == 1) { // ASCIIZ
if (len > 0) {
const char* str = *(const char**)fld;
if (str) memcpy(ptr, str, len);
ptr += len;
}
*ptr++ = '\0';
} else { // array
if (len > 0) {
const uint8_t* arr = *(const uint8_t**)fld;
if (arr) memcpy(ptr, arr, (size_t)len * f->elem_size);
ptr += (size_t)len * f->elem_size;
}
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
void* cur = *(void**)(data + f->offset);
uint16_t count = 0;
while (cur && count < 0xFFFFu) {
count++;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
ptr += length_encode(ptr, count);
cur = *(void**)(data + f->offset);
for (uint16_t i = 0; i < count; i++) {
if (!cur) break;
memcpy(ptr, cur, f->elem_size);
ptr += f->elem_size;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
}
}
*out_buf = buf;
*out_len = ptr - buf;
return SERIALIZE_ERR_OK;
}
int serialize_decode(const uint8_t* in_buf,
size_t in_len,
const struct SerializeSchema* schema,
void** structure) {
if (!in_buf || !schema || !structure) {
if (structure) *structure = NULL;
return SERIALIZE_ERR_NULL;
}
void* obj = u_malloc(schema->struct_size);
if (!obj) {
*structure = NULL;
return SERIALIZE_ERR_BUF;
}
memset(obj, 0, schema->struct_size);
const uint8_t* ptr = in_buf;
const uint8_t* end = in_buf + in_len;
uint8_t* data = (uint8_t*)obj;
size_t consumed;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_FIXED) {
if (ptr + f->elem_size > end) goto decode_error;
if (f->elem_size > 0) {
memcpy(data + f->offset, ptr, f->elem_size);
ptr += f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
if (ptr + f->elem_size > end) goto decode_error;
uint8_t* arr = NULL;
if (f->elem_size > 0) {
arr = u_malloc(f->elem_size);
if (arr) memcpy(arr, ptr, f->elem_size);
}
*(uint8_t**)(data + f->offset) = arr;
if (f->elem_size > 0 && !arr) goto decode_error;
ptr += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint16_t len = length_decode(ptr, end - ptr, &consumed);
if (consumed == 0) goto decode_error;
ptr += consumed;
int is_str = (f->elem_size == 1);
size_t needed = is_str ? (size_t)len + 1 : (size_t)len * f->elem_size;
if ((size_t)(end - ptr) < needed) goto decode_error;
if (is_str) {
char* str = NULL;
if (len == 0) {
str = u_malloc(1);
if (str) str[0] = '\0';
} else {
str = u_malloc(len + 1);
if (str) {
memcpy(str, ptr, len);
str[len] = '\0';
}
}
*(char**)(data + f->offset) = str;
if (!str) goto decode_error;
ptr += needed;
} else {
uint8_t* arr = NULL;
if (needed > 0) {
arr = u_malloc(needed);
if (arr) memcpy(arr, ptr, needed);
}
*(uint8_t**)(data + f->offset) = arr;
if (needed > 0 && !arr) goto decode_error;
ptr += needed;
}
if (f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32) {
set_var_count(data, f, len);
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
uint16_t len = length_decode(ptr, end - ptr, &consumed);
if (consumed == 0) goto decode_error;
ptr += consumed;
void* head = NULL;
void* prev = NULL;
for (uint16_t i = 0; i < len; i++) {
if (ptr + f->elem_size > end) {
// освободить уже созданную часть списка
void* n = head;
while (n) {
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
u_free(n);
n = nxt;
}
goto decode_error;
}
void* new_node = u_malloc(f->elem_size);
if (!new_node) {
void* n = head;
while (n) {
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
u_free(n);
n = nxt;
}
goto decode_error;
}
memcpy(new_node, ptr, f->elem_size);
// обязательно обнуляем next в новом узле
*(void**)((uint8_t*)new_node + f->len_offset) = NULL;
if (!head) {
head = new_node;
} else {
*(void**)((uint8_t*)prev + f->len_offset) = new_node;
}
prev = new_node;
ptr += f->elem_size;
}
*(void**)(data + f->offset) = head;
} else {
goto decode_error;
}
}
*structure = obj;
return SERIALIZE_ERR_OK;
decode_error:
serialize_free(schema, &obj);
*structure = NULL;
return SERIALIZE_ERR_BUF;
}
void serialize_free(const struct SerializeSchema* schema, void** structure) {
if (!schema || !structure || !*structure) return;
uint8_t* data = (uint8_t*)*structure;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_LINKED) {
void** headp = (void**)(data + f->offset);
void* node = *headp;
while (node) {
void* next = *(void**)((uint8_t*)node + f->len_offset);
u_free(node);
node = next;
}
*headp = NULL;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_FIXED ||
(f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32)) {
uint8_t** p = (uint8_t**)(data + f->offset);
if (*p) {
u_free(*p);
*p = NULL;
}
}
}
u_free(*structure);
*structure = NULL;
}
+130
View File
@@ -0,0 +1,130 @@
/**
* @file serialize.h
* @brief Бинарная сериализация структур с динамическим выделением памяти
*
* Библиотека преобразует C-структуру, содержащую:
* - фиксированные поля,
* - ASCIIZ-строки (char*),
* - динамические массивы (uint8_t* / char*),
* - singly-linked списки
* в компактный бинарный буфер и обратно.
*
* Поддерживаемые типы полей (см. SERIALIZE_TYPE_*):
* 0 — фиксированные данные (встроены в структуру)
* 1 — ASCIIZ-строка (char*, длина = strlen)
* 2 — массив ровно из 1 элемента (указатель)
* 3 — массив, количество элементов в UINT8 (по len_offset)
* 4 — массив, количество элементов в UINT16 (по len_offset)
* 5 — массив, количество элементов в UINT32 (по len_offset)
* 6 — singly-linked list (next-указатель по смещению len_offset)
*
* Особенности:
* • Все динамические данные (строки, массивы, списки) выделяются через u_malloc.
* • При encode длина переменных полей кодируется всегда 2 байтами (uint16_t, big-endian).
* • Для linked list в буфере сохраняется только количество узлов + сырые данные узлов
* (next-указатели НЕ сериализуются, они восстанавливаются при decode).
* • serialize_decode принимает буфер БЕЗ заголовка.
* • serialize_free освобождает ВСЮ структуру и все вложенные динамические объекты.
* • Поля в schema.fields должны идти в порядке возрастания offset.
* • max_size (если > 0) — жёсткий лимит размера выходного буфера.
*
* @example
* // 1. Описание структуры
* typedef struct Node {
* uint32_t value;
* struct Node* next; // next по offset = 4
* } Node;
*
* typedef struct {
* uint8_t id;
* char* name;
* uint8_t name_len;
* uint8_t* data;
* uint16_t data_cnt;
* Node* list; // linked list
* } MyStruct;
*
* // 2. Схема
* static const struct SerializeField my_fields[] = {
* {offsetof(MyStruct, id), 1, SERIALIZE_TYPE_FIXED, 0},
* {offsetof(MyStruct, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(MyStruct, name_len)},
* {offsetof(MyStruct, data), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(MyStruct, data_cnt)},
* {offsetof(MyStruct, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)}
* };
*
* static const struct SerializeSchema my_schema = {
* .field_count = 4,
* .struct_size = sizeof(MyStruct),
* .max_size = 4096, // опционально
* .header_len = 4,
* .fields = my_fields
* };
*
* // 3. Использование
* uint8_t header[] = {0x01, 0x02, 0x03, 0x04};
* uint8_t *buf = NULL;
* size_t len;
*
* serialize_encode(&orig, &my_schema, header, &buf, &len);
*
* MyStruct *restored = NULL;
* uint8_t *data = buf + my_schema.header_len;
* serialize_decode(data, len - my_schema.header_len, &my_schema, (void**)&restored);
*
* serialize_free(&my_schema, (void**)&restored);
* u_free(buf);
*/
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/mem.h"
#define SERIALIZE_TYPE_FIXED 0 ///< фиксированные данные, elem_size = размер
#define SERIALIZE_TYPE_ASCIIZ 1 ///< char* (ASCIIZ, длина = strlen)
#define SERIALIZE_TYPE_ARRAY_FIXED 2 ///< *array ровно 1 элемент
#define SERIALIZE_TYPE_ARRAY_U8 3 ///< *array, счётчик UINT8
#define SERIALIZE_TYPE_ARRAY_U16 4 ///< *array, счётчик UINT16
#define SERIALIZE_TYPE_ARRAY_U32 5 ///< *array, счётчик UINT32
#define SERIALIZE_TYPE_LINKED 6 ///< linked_list (next по смещению len_offset)
/**
* @brief Описание одного поля структуры для сериализации
*/
struct SerializeField {
uint16_t offset; ///< смещение поля в структуре
uint8_t elem_size; ///< размер элемента (для fixed = размер данных, для array/linked = размер одного элемента)
uint8_t type; ///< SERIALIZE_TYPE_*
uint16_t len_offset; ///< смещение поля-счётчика (игнорируется для type 0,1,2)
};
/**
* @brief Схема структуры для сериализации
*/
struct SerializeSchema {
uint16_t field_count; ///< число полей
uint16_t struct_size; ///< размер основной структуры
uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения)
uint16_t header_len; ///< длина заголовка
const struct SerializeField* fields;
};
#define SERIALIZE_ERR_OK 0 ///< успех
#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти
#define SERIALIZE_ERR_NULL -2 ///< NULL-указатель
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len);
int serialize_decode(const uint8_t* in_buf, size_t in_len,
const struct SerializeSchema* schema,
void** structure);
void serialize_free(const struct SerializeSchema* schema, void** structure);
#endif // SERIALIZE_H
+16 -1
View File
@@ -211,7 +211,7 @@ int socket_set_mark(socket_t sock, int mark) {
}
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
struct sockaddr* dest, socklen_t dest_len) {
const struct sockaddr* dest, socklen_t dest_len) {
#ifdef _WIN32
return sendto(sock, (const char*)buf, (int)len, 0, dest, dest_len);
#else
@@ -230,3 +230,18 @@ ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
return recvfrom(sock, buf, len, 0, src, src_len);
#endif
}
uint16_t ss_get_port(const struct sockaddr_storage *addr) {
if (!addr) return 0;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
return ntohs(ipv4->sin_port);
}
else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
return ntohs(ipv6->sin6_port);
}
return 0;
}
+5 -1
View File
@@ -5,6 +5,7 @@
#ifndef SOCKET_COMPAT_H
#define SOCKET_COMPAT_H
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
@@ -62,11 +63,14 @@ int socket_set_mark(socket_t sock, int mark);
// I/O operations
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
struct sockaddr* dest, socklen_t dest_len);
const struct sockaddr* dest, socklen_t dest_len);
ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
struct sockaddr* src, socklen_t* src_len);
// Utility
const char* socket_strerror(int err);
// взять порт из sockaddr_storage
uint16_t ss_get_port(const struct sockaddr_storage *addr);
#endif // SOCKET_COMPAT_H
+99
View File
@@ -0,0 +1,99 @@
/* sliding_window_min.c */
#include "swm_min.h"
#include <stdlib.h>
#include <limits.h>
struct SlidingWindowMin {
int* data; /* кольцевой буфер значений */
int* deq; /* deque индексов (тоже кольцевой) */
int dq_head;
int dq_tail;
int deq_count;
int cur_pos;
int window_size;
};
SlidingWindowMin* swm_create(int window_size)
{
if (window_size <= 0) {
return NULL;
}
SlidingWindowMin* swm = (SlidingWindowMin*)malloc(sizeof(SlidingWindowMin));
if (!swm) {
return NULL;
}
swm->data = (int*)malloc((size_t)window_size * sizeof(int));
if (!swm->data) {
free(swm);
return NULL;
}
swm->deq = (int*)malloc((size_t)window_size * sizeof(int));
if (!swm->deq) {
free(swm->data);
free(swm);
return NULL;
}
swm->window_size = window_size;
swm->dq_head = 0;
swm->dq_tail = 0;
swm->deq_count = 0;
swm->cur_pos = 0;
return swm;
}
void swm_destroy(SlidingWindowMin* swm)
{
if (swm) {
free(swm->data);
free(swm->deq);
free(swm);
}
}
void swm_add(SlidingWindowMin* swm, int val)
{
if (!swm) return;
int idx = swm->cur_pos;
int w = swm->window_size;
/* записываем в кольцевой буфер */
swm->data[idx % w] = val;
/* 1. удаляем из начала deque индексы, которые уже вышли за окно */
while (swm->deq_count > 0 && swm->deq[swm->dq_head] <= idx - w) {
swm->dq_head = (swm->dq_head + 1) % w;
swm->deq_count--;
}
/* 2. удаляем из хвоста все элементы, которые хуже текущего */
while (swm->deq_count > 0) {
int last_idx = swm->deq[(swm->dq_tail - 1 + w) % w];
if (swm->data[last_idx % w] < val) {
break;
}
swm->dq_tail = (swm->dq_tail - 1 + w) % w;
swm->deq_count--;
}
/* 3. добавляем текущий индекс */
swm->deq[swm->dq_tail] = idx;
swm->dq_tail = (swm->dq_tail + 1) % w;
swm->deq_count++;
swm->cur_pos++;
}
int swm_get_min(const SlidingWindowMin* swm)
{
if (!swm || swm->deq_count == 0) {
return INT_MAX;
}
int min_idx = swm->deq[swm->dq_head];
return swm->data[min_idx % swm->window_size];
}
+41
View File
@@ -0,0 +1,41 @@
/* sliding_window_min.h */
#ifndef SLIDING_WINDOW_MIN_H
#define SLIDING_WINDOW_MIN_H
#include <stddef.h> /* для size_t */
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SlidingWindowMin SlidingWindowMin;
/**
* Создаёт новую структуру скользящего минимума.
* @param window_size размер окна (должен быть > 0)
* @return указатель на структуру или NULL при ошибке выделения памяти
*/
SlidingWindowMin* swm_create(int window_size);
/**
* Уничтожает структуру и освобождает всю динамически выделенную память.
*/
void swm_destroy(SlidingWindowMin* swm);
/**
* Добавляет новое значение в окно.
* После вызова swm_get_min() сразу вернёт минимум последних min(window_size, добавленных) значений.
*/
void swm_add(SlidingWindowMin* swm, int val);
/**
* Возвращает текущий минимум в окне.
* Если окно пустое — возвращает INT_MAX.
*/
int swm_get_min(const SlidingWindowMin* swm);
#ifdef __cplusplus
}
#endif
#endif /* SLIDING_WINDOW_MIN_H */
+112 -85
View File
@@ -11,21 +11,21 @@
#define LEFT_CHILD(i) (2 * (i))
#define RIGHT_CHILD(i) (2 * (i) + 1)
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
if (!h) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
u_free(h);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
if (!h) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
u_free(h);
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
h->size = 0;
h->capacity = initial_capacity;
h->freed_count = 0;
@@ -50,70 +50,81 @@ void timeout_heap_destroy(TimeoutHeap *h) {
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
// Swap with parent
TimeoutEntry temp = h->heap[PARENT(i) - 1];
h->heap[PARENT(i) - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = PARENT(i);
}
static void update_index(TimeoutHeap *h, size_t idx) {
if (h->heap[idx].index_ptr)
*h->heap[idx].index_ptr = idx;
}
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) {
TimeoutEntry temp = h->heap[a];
h->heap[a] = h->heap[b];
h->heap[b] = temp;
update_index(h, a);
update_index(h, b);
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
swap_entries(h, PARENT(i) - 1, i - 1);
i = PARENT(i);
}
}
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
h->heap[idx].deleted = 0;
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
h->heap[idx].index_ptr = index_ptr;
h->heap[idx].deleted = 0;
if (index_ptr)
*index_ptr = idx;
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
// Swap
TimeoutEntry temp = h->heap[smallest - 1];
h->heap[smallest - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = smallest;
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
swap_entries(h, smallest - 1, i - 1);
i = smallest;
}
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
h->heap[0] = h->heap[--h->size];
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
h->heap[0] = h->heap[--h->size];
update_index(h, 0);
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
}
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
@@ -149,18 +160,34 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
return 0;
}
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
for (size_t i = 0; i < h->size; ++i) {
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
h->heap[i].deleted = 1;
return 0;
}
}
return -1; // Not found
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
for (size_t i = 0; i < h->size; ++i) {
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
h->heap[i].deleted = 1;
h->heap[i].expiration = 0;
bubble_up(h, i + 1);
return 0;
}
}
return -1; // Not found
}
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) {
if (index >= h->size || h->heap[index].data != data) return -1;
h->heap[index].deleted = 1;
h->heap[index].expiration = 0;
bubble_up(h, index + 1);
return 0;
}
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
}
size_t timeout_heap_get_size(TimeoutHeap *h) {
if (!h) return 0;
return h->size;
}
+6 -1
View File
@@ -11,6 +11,7 @@ typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
typedef struct {
TimeoutTime expiration; // Sort key (smaller = earlier)
void *data; // User data (e.g., callback or ID)
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
int deleted; // 0 = active, 1 = deleted
} TimeoutEntry;
@@ -53,7 +54,7 @@ void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*call
* @param data User data associated with the timeout.
* @return 0 on success, -1 on allocation failure.
*/
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data);
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr);
/**
* Peek at the earliest non-deleted timeout without removing it.
@@ -82,6 +83,8 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
*/
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data);
/**
* Get the number of freed timer nodes.
* @param h The heap.
@@ -89,4 +92,6 @@ int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
*/
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
size_t timeout_heap_get_size(TimeoutHeap *h);
#endif // TIMEOUT_HEAP_H
+260 -230
View File
@@ -32,11 +32,13 @@
// Timeout node with safe cancellation
struct timeout_node {
char name[16];
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
struct timeout_node* next; // For immediate queue (FIFO)
size_t heap_index; // Position in timeout_heap (SIZE_MAX if not in heap)
};
// Socket node with array-based storage
@@ -227,11 +229,17 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
// Mark as inactive
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->sockets[index].sock = SOCKET_INVALID;
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
// Mark as inactive and clear all pointers
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->sockets[index].sock = SOCKET_INVALID;
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
sa->sockets[index].read_cbk = NULL;
sa->sockets[index].write_cbk = NULL;
sa->sockets[index].read_cbk_sock = NULL;
sa->sockets[index].write_cbk_sock = NULL;
sa->sockets[index].except_cbk = NULL;
sa->sockets[index].user_data = NULL;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
@@ -319,12 +327,27 @@ uint64_t get_time_tb(void) {
// QueryPerformanceCounter(&count); // Получаем текущее значение счётчика
// return (uint64_t)(count.QuadPart * 10000ULL) / (uint64_t)freq.QuadPart; // Преобразуем в требуемые единицы времени
//}
#else
uint64_t get_time_tb(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
}
#else
uint64_t get_time_tb(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
}
#endif
#ifdef _WIN32
uint64_t get_time_us(void) {
LARGE_INTEGER freq, count;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&count);
return (uint64_t)(count.QuadPart * 1000000ULL / freq.QuadPart);
}
#else
uint64_t get_time_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}
#endif
@@ -491,32 +514,39 @@ static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback, const char* name) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
struct timeout_node* node = memory_pool_alloc(ua->timeout_pool);
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
node->arg = arg;
node->callback = callback;
node->ua = ua;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
}
ua->timer_alloc_count++;
if (name) {
strncpy(node->name, name, sizeof(node->name) - 1);
node->name[sizeof(node->name) - 1] = '\0';
} else {
node->name[0] = '\0';
}
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->heap_index = SIZE_MAX;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node, &node->heap_index) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
memory_pool_free(ua->timeout_pool, node);
ua->timer_free_count++; // Balance the alloc counter
@@ -538,11 +568,13 @@ void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t cal
}
ua->timer_alloc_count++;
node->name[0] = '\0';
node->arg = user_arg;
node->callback = callback;
node->ua = ua;
node->expiration_ms = 0;
node->next = NULL;
node->heap_index = SIZE_MAX;
// FIFO: добавляем в конец очереди
if (ua->immediate_queue_tail) {
@@ -571,28 +603,29 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id) {
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully marked as deleted - u_free will happen lazily in heap
node->callback = NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap", node);
return ERR_OK;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// If not found in heap, it may have already expired or been invalid
return ERR_FAIL;
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
if (node->heap_index == SIZE_MAX || node->heap_index >= ua->timeout_heap->size ||
ua->timeout_heap->heap[node->heap_index].data != node) {
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
return ERR_FAIL;
}
if (timeout_heap_cancel_at(ua->timeout_heap, node->heap_index, node) == 0) {
node->heap_index = SIZE_MAX;
node->callback = NULL;
return ERR_OK;
}
return ERR_FAIL;
}
@@ -618,10 +651,10 @@ void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, s
if (read_cbk) ev.events |= EPOLLIN;
if (write_cbk) ev.events |= EPOLLOUT;
// Use level-triggered mode (default) for compatibility with UDP sockets
ev.data.ptr = &ua->sockets->sockets[index];
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
// Failed to add to epoll - remove from socket array and return error
ev.data.fd = fd;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
// Failed to add to epoll - remove from socket array and return error
socket_array_remove(ua->sockets, fd);
ua->socket_alloc_count--;
return NULL;
@@ -675,14 +708,13 @@ void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t
ev.events = 0;
if (read_cbk) ev.events |= EPOLLIN;
if (write_cbk) ev.events |= EPOLLOUT;
ev.data.ptr = &ua->sockets->sockets[index];
// On Windows, need to cast socket_t to int for epoll_ctl
#ifdef _WIN32
int fd = (int)(intptr_t)sock;
#else
int fd = sock;
#endif
// On Windows, need to cast socket_t to int for epoll_ctl
#ifdef _WIN32
int fd = (int)(intptr_t)sock;
#else
int fd = sock;
#endif
ev.data.fd = fd;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
socket_array_remove(ua->sockets, fd);
ua->socket_alloc_count--;
@@ -797,49 +829,59 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
#if HAS_EPOLL
static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, int n_events) {
for (int i = 0; i < n_events; i++) {
// Check if this is the wakeup fd (data.ptr is NULL)
if (events[i].data.ptr == NULL) {
if (events[i].events & EPOLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
// Socket event
struct socket_node* node = (struct socket_node*)events[i].data.ptr;
if (!node || !node->active) continue;
/* Check for error conditions first */
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLIN) {
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock) {
node->read_cbk_sock(node->sock, node->user_data);
}
} else {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
}
/* Write readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLOUT) {
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->write_cbk_sock) {
node->write_cbk_sock(node->sock, node->user_data);
}
} else {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
// Check if this is the wakeup fd (data.fd is -1)
if (events[i].data.fd < 0) {
if (events[i].events & EPOLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
// Socket event — save node fields locally: callbacks may realloc
// the socket array, invalidating `node`.
struct socket_node* node = socket_array_get(ua->sockets, events[i].data.fd);
if (!node || !node->active) continue;
int local_fd = node->fd;
socket_t local_sock = node->sock;
int local_type = node->type;
void* local_ud = node->user_data;
socket_callback_t local_except = node->except_cbk;
socket_callback_t local_read = node->read_cbk;
socket_callback_t local_write = node->write_cbk;
socket_t_callback_t local_read_sock = node->read_cbk_sock;
socket_t_callback_t local_write_sock = node->write_cbk_sock;
/* Check for error conditions first */
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
if (local_except) {
local_except(local_fd, local_ud);
}
}
/* Read readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLIN) {
if (local_type == SOCKET_NODE_TYPE_SOCK) {
if (local_read_sock) {
local_read_sock(local_sock, local_ud);
}
} else {
if (local_read) {
local_read(local_fd, local_ud);
}
}
}
/* Write readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLOUT) {
if (local_type == SOCKET_NODE_TYPE_SOCK) {
if (local_write_sock) {
local_write_sock(local_sock, local_ud);
}
} else {
if (local_write) {
local_write(local_fd, local_ud);
}
}
}
}
}
@@ -867,20 +909,19 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
}
// Use minimum of requested and next timer if both finite
struct timeval poll_timeout;
if (timeout_tb < 0) {
// Infinite requested - use next timer if any
poll_timeout = next_timeout;
} else {
// Finite requested - min of requested and next
if (next_timeout.tv_sec < req_timeout.tv_sec ||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
poll_timeout = next_timeout;
} else {
poll_timeout = req_timeout;
}
}
struct timeval poll_timeout;
if (timeout_tb < 0) {
poll_timeout = next_timeout;
} else {
if (next_timeout.tv_sec < req_timeout.tv_sec ||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
poll_timeout = next_timeout;
} else {
poll_timeout = req_timeout;
}
}
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
int timeout_ms;
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {
@@ -1185,9 +1226,9 @@ struct UASYNC* uasync_create(void) {
ua->immediate_queue_head = NULL;
ua->immediate_queue_tail = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA...");
ua->sockets = socket_array_create(16);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA1...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA...");
ua->sockets = socket_array_create(16);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA1...");
if (!ua->sockets) {
if (ua->wakeup_initialized) {
#ifdef _WIN32
@@ -1239,7 +1280,7 @@ struct UASYNC* uasync_create(void) {
// Set callback to u_free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
// Initialize epoll on Linux
ua->epoll_fd = -1;
ua->use_epoll = 0;
@@ -1252,7 +1293,7 @@ struct UASYNC* uasync_create(void) {
if (ua->wakeup_initialized) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = NULL; // NULL ptr indicates wakeup fd
ev.data.fd = -1; // -1 fd indicates wakeup
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
}
@@ -1262,7 +1303,7 @@ struct UASYNC* uasync_create(void) {
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
#ifdef _WIN32
// Windows: self-connected UDP socket for wakeup
@@ -1308,8 +1349,19 @@ struct UASYNC* uasync_create(void) {
u_free(ua);
return NULL;
}
ua->wakeup_initialized = 1;
ua->wakeup_initialized = 1;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = -1;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
}
}
#endif
fcntl(ua->wakeup_pipe[0], F_SETFL, fcntl(ua->wakeup_pipe[0], F_GETFL, 0) | O_NONBLOCK);
fcntl(ua->wakeup_pipe[1], F_SETFL, fcntl(ua->wakeup_pipe[1], F_GETFL, 0) | O_NONBLOCK);
@@ -1319,84 +1371,84 @@ struct UASYNC* uasync_create(void) {
pthread_mutex_init(&ua->posted_lock, NULL);
#endif
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
printf(" Timer: node=%p, expires=%llu ms\n",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration);
}
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: NULL uasync instance", prefix);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: UASYNC Resource Report for %p", prefix, ua);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer: node=%p, expires=%llu ms",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration);
}
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket array capacity: %d, active: %d",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket: fd=%d, active=%d",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Total active sockets: %d", active_sockets);
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: End of resource report", prefix);
}
// Modified function in u_async.c: uasync_destroy
// Changes: Close wakeup sockets properly on Windows.
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts
// Очистить immediate_queue
@@ -1412,7 +1464,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
// Очистить heap
if (ua->timeout_heap) {
size_t u_freed_count = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
@@ -1512,31 +1563,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
socket_platform_cleanup();
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_pool) {
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
}
if (!ua->immediate_queue_head) {
ua->immediate_queue_head = NULL;
ua->immediate_queue_tail = NULL;
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_u_free, size_t* socket_alloc, size_t* socket_u_free) {
if (!ua) return;
@@ -1625,8 +1651,12 @@ int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id) {
return (*socket_id != NULL) ? 0 : -1;
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1); // Infinite wait
}
void uasync_stop(struct UASYNC* ua) {
if (ua) ua->stop = 1;
}
void uasync_mainloop(struct UASYNC* ua) {
while (!ua->stop) {
uasync_poll(ua, -1);
}
}
+11 -7
View File
@@ -5,9 +5,10 @@
#ifndef UASYNC_H
#define UASYNC_H
#include "platform_compat.h"
#include <stddef.h>
#include "timeout_heap.h"
#include "platform_compat.h"
#include <stddef.h>
#include <signal.h>
#include "timeout_heap.h"
#include "socket_compat.h"
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
@@ -66,8 +67,9 @@ struct UASYNC {
#ifdef _WIN32
CRITICAL_SECTION posted_lock;
#else
pthread_mutex_t posted_lock;
#endif
pthread_mutex_t posted_lock;
#endif
volatile sig_atomic_t stop;
};
// Type definitions
@@ -77,13 +79,14 @@ typedef struct UASYNC UASYNC_t;
// Instance API - основной API для работы с uasync
struct UASYNC* uasync_create(void);
void uasync_destroy(struct UASYNC* ua, int close_fds);
void uasync_init_instance(struct UASYNC* ua);
// текущее время (timebase 0.1ms)
uint64_t get_time_tb(void);
// текущее время в микросекундах (для burst-измерений)
uint64_t get_time_us(void);
// Timeouts, timebase = 0.1 mS
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
// Immediate execution in next mainloop (FIFO order)
@@ -102,6 +105,7 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(struct UASYNC* ua);
void uasync_stop(struct UASYNC* ua);
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
+219
View File
@@ -0,0 +1,219 @@
Диаграмма вызовов функций при определении типа NAT (NAT detection)
=====================================================================
=== ИНИЦИАЛИЗАЦИЯ BGP ===
route_bgp_init(instance)
└── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL)
└── При создании нового ETCP_CONN:
route_bgp_etcp_conn_cbk(conn, arg)
├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp)
└── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_REQUEST
├── etcp_connection_create(instance, name) [если новый peer]
│ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk]
├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1)
├── Отправляет ETCP_INIT_RESPONSE (с NAT info)
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── conn->ready_cbk
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_RESPONSE
├── Сохраняет NAT address: link->nat_ip, link->nat_port
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) ===
route_bgp_on_conn_up(etcp, arg)
└── route_bgp_new_conn(conn)
├── route_bgp_add_to_senders(bgp, conn)
└── Сканирует ВСЕ линки ВСЕХ соединений:
if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS)
└── route_bgp_start_link_nat_check(bgp, link)
├── if (nat_check_status == IN_PROGRESS) return
├── route_bgp_find_third_node(bgp, exclude=link->etcp)
│ └── Ищет другой ETCP_CONN в senders_list
├── Определяет target_ip/target_port:
│ ├── Если link->nat_ip != 0: использует nat_ip/nat_port
│ └── Иначе: использует link->remote_addr
├── Проверяет is_local_subnet(target_ip) [если запрещено - skip]
├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port)
│ └── Ищет первый линк с nat_ip != 0
├── Создает nat_check_arg { link, nat_ip, nat_port }
└── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port,
count=3, interval=500ms, timeout=1000ms,
wait_timeout=5000ms,
cb=nat_link_check_cb, arg=nat_check_arg,
pubkey=peer_pubkey)
=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) ===
route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...)
├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey }
├── etcp_send(to_conn, entry) [отправляет запрос third node]
├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer }
└── Добавляет в bgp->ping_pending
=== ОБРАБОТКА PING_REQ (на third node) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_REQ
└── route_ping_handle_req(bgp, from_conn, data, len)
├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total }
├── Находит первый IPv4 ETCP_SOCKET -> local_sock
└── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr,
timeout_ms, route_ping_single_cb, ctx, NULL, 0)
└── Отправляет ETCP пинг на target_addr
=== ОТВЕТ НА ПИНГ (целевой узел) ===
etcp_connections_read_callback_socket()
└── Получает ETCP пинг -> отправляет pong
=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) ===
route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len)
├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt
├── Если count_sent < count_total:
│ └── etcp_send_ping_to_socket(...) [следующий пинг]
└── Иначе:
└── route_ping_series_finish(ctx)
├── Вычисляет avg_rtt
└── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt }
└── etcp_send(reply_conn, entry) [отправляет результат инициатору]
=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_RESP
└── route_ping_handle_resp(bgp, from_conn, data, len)
├── Ищет route_ping_pending по request_id
├── Отменяет timeout_timer
└── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
=== CALLBACK ПРОВЕРКИ NAT ===
nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED
├── link->nat_type = nat_type
├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED
└── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type)
├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type }
└── etcp_send(conn, entry) [отправляет результат клиенту]
=== ПРИЕМ NAT_INFO (на клиенте) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_NAT_INFO
└── route_bgp_handle_nat_info(bgp, from_conn, data, len)
├── Ищет линки по remote_socket_id
├── Устанавливает link->nat_type = info->nat_type
└── Логирует результат NAT detection
=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) ===
route_bgp_send_nat_check_req(conn, socket_id)
├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port }
│ ├── Ищет линк с remote_socket_id == socket_id
│ └── Берет interface_addr из линка -> interface_ip/interface_port
└── etcp_send(conn, entry)
route_bgp_handle_nat_check_req(bgp, from_conn, data, len)
├── Принимает BGP_NAT_CHECK_REQ
├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy
├── Ищет линк с remote_socket_id == req->socket_id
└── Если линк найден и не в процессе проверки:
└── route_bgp_start_link_nat_check(bgp, target_link)
[далее по стандартному flow]
=== СВЯЗЬ СТРУКТУР ===
struct ETCP_SOCKET
├── local_addr [адрес бинда из конфига]
├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг]
└── ...
struct ETCP_LINK
├── remote_addr [адрес удаленного узла]
├── nat_ip [NAT IP, полученный из INIT_RESPONSE]
├── nat_port [NAT port, полученный из INIT_RESPONSE]
├── nat_type [OPEN/RESTRICTED, результат проверки]
├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED]
├── initialized [0/1, флаг завершения handshake]
└── ...
struct ETCP_CONN
├── links [список ETCP_LINK]
├── up_cbk [route_bgp_on_conn_up]
├── down_cbk [route_bgp_on_conn_down]
├── ready_cbk [вызывается при etcp_conn_ready]
├── initialized [0/1, флаг готовности]
├── links_up [0/1, есть ли активные линки]
└── ...
=== СТАТУСЫ NAT CHECK ===
NAT_CHECK_NONE = 0 [не проверялся]
NAT_CHECK_WAITING = 1 [ожидает]
NAT_CHECK_IN_PROGRESS = 2 [проверка запущена]
NAT_CHECK_OPEN = 3 [результат: OPEN]
NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED]
NAT_TYPE_UNKNOWN = 0
NAT_TYPE_OPEN = 1
NAT_TYPE_RESTRICTED = 2
NAT_VERIFIED_UNKNOWN = 4
NAT_VERIFIED_OPEN = 5
NAT_VERIFIED_RESTRICTED = 6
NAT_VERIFIED_DIRECT = 7
=== СООБЩЕНИЯ ПРОТОКОЛА ===
ETCP_INIT_REQUEST (0x02) [клиент -> сервер]
ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info]
ETCP_INIT_REQUEST_NOINIT (0x04)
ETCP_INIT_RESPONSE_NOINIT (0x05)
ETCP_PING (0x06)
ETCP_PONG (0x07)
ROUTE_SUBCMD_NODEINFO (0x04)
ROUTE_SUBCMD_REQUEST_TABLE (0x05)
ROUTE_SUBCMD_WITHDRAW (0x06)
ROUTE_SUBCMD_PING_REQ (0x07)
ROUTE_SUBCMD_PING_RESP (0x08)
ROUTE_SUBCMD_NAT_INFO (0x09)
ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A)
=== ПОЛНЫЙ FLOW (одна строка) ===
Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info
NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow]
+1 -1
View File
@@ -376,7 +376,7 @@ static void socket_callback(int fd, void *user_arg) {
u_free(packet);
return;
}
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet);
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet, "netem_delay");
}
// Callback for sending delayed packet
+18 -1
View File
@@ -7,8 +7,11 @@ utun_CORE_SOURCES = \
config_parser.c \
config_updater.c \
route_lib.c \
route6_lib.c \
route_bgp.c \
route_ping.c \
route_node.c \
route_connectivity.c \
routing.c \
tun_if.c \
tun_route.c \
@@ -19,13 +22,26 @@ utun_CORE_SOURCES = \
etcp_connections.c \
etcp_loadbalancer.c \
etcp_debug.c \
etcp_dump.c \
secure_channel.c \
crc32.c \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
control_server.c \
firewall.c
firewall.c \
eim_nat.c \
nat_transport.c \
dummynet.c \
tcp_proxy.c \
etcp_router.c \
remote_proxy.c \
udp_proxy.c \
icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \
lwip_tcp/lwip_tcp_out.c
# Platform-specific TUN libs (Windows only)
utun_TUN_LIBS = @TUN_LIBS@
@@ -56,6 +72,7 @@ endif
# Include paths
utun_CORE_CFLAGS = \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/src/uip \
-I$(top_srcdir)/tinycrypt/lib/include \
-I$(top_srcdir)/tinycrypt/lib/source \
-g \
+360 -42
View File
@@ -13,6 +13,7 @@
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#endif
#include "../lib/mem.h"
@@ -23,6 +24,16 @@
static int assign_string(char *dest, size_t dest_size, const char *src);
static int parse_firewall_rule(const char *rule_str, struct global_config *global);
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
for (size_t i = 0; i < binary_len; i++) {
unsigned int byte;
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
binary[i] = (uint8_t)byte;
}
return 0;
}
typedef enum {
SECTION_UNKNOWN,
SECTION_GLOBAL,
@@ -30,7 +41,12 @@ typedef enum {
SECTION_CLIENT,
SECTION_ROUTING,
SECTION_DEBUG,
SECTION_FIREWALL
SECTION_FIREWALL,
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY,
SECTION_REMOTE_PROXY
} section_type_t;
static char* trim(char *str) {
@@ -109,9 +125,21 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
char clean_addr[MAX_ADDR_LEN];
const char *a = addr_str;
if (addr_str[0] == '[') {
size_t len = strlen(addr_str);
if (len > 2 && addr_str[len - 1] == ']') {
if (len - 2 >= sizeof(clean_addr)) return -1;
memcpy(clean_addr, addr_str + 1, len - 2);
clean_addr[len - 2] = '\0';
a = clean_addr;
}
}
struct addrinfo *result;
if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) {
if (getaddrinfo(a, port_str, &hints, &result) != 0) {
return -1;
}
@@ -258,6 +286,51 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
return 0;
}
static int parse_control_allow(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
trim(rule_copy);
uint8_t cidr = 32;
char *slash = strchr(rule_copy, '/');
if (slash) {
*slash = '\0';
cidr = (uint8_t)atoi(slash + 1);
if (cidr > 32) cidr = 32;
}
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr);
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
if (!new_allows) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows");
return -1;
}
global->control_allows = new_allows;
}
struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count];
rule->network = ip & mask;
rule->netmask = mask;
global->control_allow_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr);
return 0;
}
static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) {
struct CFG_SERVER *srv = servers;
while (srv) {
@@ -269,7 +342,7 @@ static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const
return NULL;
}
static int parse_global(const char *key, const char *value, struct global_config *global) {
static int parse_global(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "my_node_name") == 0) {
return assign_string(global->name, sizeof(global->name), value);
}
@@ -303,29 +376,14 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "control_ip") == 0) {
// Store control_ip for later processing with control_port
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
if (strcmp(key, "inflight_min_bytes") == 0) {
global->inflight_min_bytes = atoi(value);
return 0;
}
if (strcmp(key, "control_port") == 0) {
// Use control_ip from config, fallback to localhost if not set
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
if (strcmp(key, "inflight_max_bytes") == 0) {
global->inflight_max_bytes = atoi(value);
return 0;
}
if (strcmp(key, "net_debug") == 0) {
global->net_debug = atoi(value);
}
if (strcmp(key, "debug_level") == 0) {
return assign_string(global->debug_level, sizeof(global->debug_level), value);
}
@@ -353,11 +411,189 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, inflight_min_bytes, inflight_max_bytes, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) {
static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
return 0;
}
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
}
if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) {
return parse_control_allow(value, global);
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown control option '%s'. Valid: ip/control_ip, port/control_port, allow/control_allow", filename, line_num, key);
return -1;
}
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "enabled") == 0) {
global->tcp_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "tun_name") == 0) {
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
return 0;
}
if (strcmp(key, "mtu") == 0) {
global->tcp_proxy_mtu = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
return -1;
}
char buf[256];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* local_port_str = strtok(buf, " ");
char* arrow = strtok(NULL, " ");
char* remote_str = strtok(NULL, "");
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
return -1;
}
int local_port = atoi(local_port_str);
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
char* remote_ip_str = strtok(remote_str, ":");
char* remote_port_str = strtok(NULL, "");
if (!remote_ip_str || !remote_port_str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
return -1;
}
int remote_port = atoi(remote_port_str);
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
int idx = global->tcp_proxy_mapping_count;
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_mapping_count++;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
return -1;
}
static int parse_nat(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "tun_ifname") == 0) {
strncpy(global->nat_tun_ifname, value, sizeof(global->nat_tun_ifname) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
uint8_t netmask;
parse_ip_with_netmask(value, &global->nat_tun_ip, &netmask);
return 0;
}
if (strcmp(key, "nat_via") == 0) {
global->nat_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "port_start") == 0) {
global->nat_port_start = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "port_end") == 0) {
global->nat_port_end = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->nat_forward_count >= MAX_NAT_FORWARDS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many NAT forward rules (max %d)", MAX_NAT_FORWARDS);
return -1;
}
// Format: proto:internal_ip:internal_port:external_port
// Example: tcp:192.168.1.100:8080:8080
char buf[128];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* proto_str = strtok(buf, ":");
char* ip_str = strtok(NULL, ":");
char* int_port = strtok(NULL, ":");
char* ext_port = strtok(NULL, ":");
if (!proto_str || !ip_str || !int_port || !ext_port) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward format: %s (expected proto:ip:internal_port:external_port)", value);
return -1;
}
uint8_t proto;
if (strcasecmp(proto_str, "tcp") == 0) proto = IPPROTO_TCP;
else if (strcasecmp(proto_str, "udp") == 0) proto = IPPROTO_UDP;
else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward proto: %s (tcp/udp)", proto_str);
return -1;
}
struct in_addr addr;
if (inet_pton(AF_INET, ip_str, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward IP: %s", ip_str);
return -1;
}
int idx = global->nat_forward_count;
global->nat_forwards[idx].proto = proto;
global->nat_forwards[idx].internal_ip_host = ntohl(addr.s_addr);
global->nat_forwards[idx].internal_port_net = htons((uint16_t)atoi(int_port));
global->nat_forwards[idx].external_port = (uint16_t)atoi(ext_port);
global->nat_forward_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "NAT forward: %s %s:%s -> :%s",
proto_str, ip_str, int_port, ext_port);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown NAT option '%s'. Valid: tun_ifname, tun_ip, nat_via, port_start, port_end, forward", filename, line_num, key);
return -1;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv, const char *filename, int line_num) {
if (strcmp(key, "addr") == 0) {
if (strncmp(value, "temporary_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_TEMPORARY;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 0;
}
if (strncmp(value, "permanent_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_PERMANENT;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 0;
}
return parse_address_and_port(value, &srv->ip);
}
if (strcmp(key, "so_mark") == 0) {
@@ -388,16 +624,15 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->mtu = atoi(value);
return 0;
}
if (strcmp(key, "loss_rate") == 0) {
srv->loss_rate = atoi(value);
if (srv->loss_rate < 0) srv->loss_rate = 0;
if (srv->loss_rate > 100) srv->loss_rate = 100;
if (strcmp(key, "only_local") == 0) {
srv->only_local = atoi(value) ? 1 : 0;
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
return -1;
}
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) {
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers, const char *filename, int line_num) {
if (strcmp(key, "link") == 0) {
char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN];
if (strlen(value) >= sizeof(link_copy)) return -1;
@@ -435,7 +670,8 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown client option '%s'. Valid: link, peer_public_key, keepalive", filename, line_num, key);
return -1;
}
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
@@ -455,6 +691,11 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -482,9 +723,14 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// Set default values
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.inflight_min_bytes = 2000; // Default 2KB
cfg->global.inflight_max_bytes = 100000; // Default 100KB
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
cfg->global.control_allows = NULL;
cfg->global.control_allow_count = 0;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -543,18 +789,18 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
switch (cur_section) {
case SECTION_GLOBAL:
if (parse_global(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid global key '%s'", filename, line_num, key);
if (parse_global(key, value, &cfg->global, filename, line_num) < 0) {
// parse_global already printed the error
}
break;
case SECTION_SERVER:
if (cur_server && parse_server(key, value, cur_server) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid server key '%s'", filename, line_num, key);
if (cur_server && parse_server(key, value, cur_server, filename, line_num) < 0) {
// parse_server already printed the error
}
break;
case SECTION_CLIENT:
if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid client key '%s'", filename, line_num, key);
if (cur_client && parse_client(key, value, cur_client, cfg->servers, filename, line_num) < 0) {
// parse_client already printed the error
}
break;
case SECTION_ROUTING:
@@ -562,6 +808,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
add_route_entry(&cfg->route_subnets, value);
} else if (strcmp(key, "my_subnet") == 0) {
add_route_entry(&cfg->my_subnets, value);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown routing option '%s'. Valid: route_subnet, my_subnet", filename, line_num, key);
}
break;
case SECTION_DEBUG:
@@ -581,8 +829,54 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
if (parse_firewall_rule(value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown firewall option '%s'. Valid: allow", filename, line_num, key);
}
break;
case SECTION_CONTROL:
if (parse_control(key, value, &cfg->global, filename, line_num) < 0) {
// parse_control already printed the error
}
break;
case SECTION_ALLOWED_KEYS:
if (strcmp(key, "allow_all") == 0) {
cfg->global.allowed_keys_allow_all = atoi(value) ? 1 : 0;
} else if (strcmp(key, "key") == 0) {
struct CFG_ALLOWED_KEY *new_key = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY));
if (!new_key) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate allowed key");
} else if (strlen(value) != SC_PUBKEY_SIZE * 2) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key length %zu, expected %d", filename, line_num, strlen(value), SC_PUBKEY_SIZE * 2);
u_free(new_key);
} else if (hex_to_binary(value, new_key->key_bin, SC_PUBKEY_SIZE) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid hex key format", filename, line_num);
u_free(new_key);
} else {
new_key->next = cfg->global.allowed_keys;
cfg->global.allowed_keys = new_key;
cfg->global.allowed_keys_count++;
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'. Valid: allow_all, key", filename, line_num, key);
}
break;
case SECTION_NAT:
cfg->global.nat_enabled = 1;
if (parse_nat(key, value, &cfg->global, filename, line_num) < 0) {
// parse_nat already printed the error
}
break;
case SECTION_TCP_PROXY:
cfg->global.tcp_proxy_enabled = 1;
if (parse_tcp_proxy(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy already printed the error
}
break;
case SECTION_REMOTE_PROXY:
cfg->global.remote_proxy_enabled = 1;
// remote_proxy section has no options — its presence alone enables it
(void)key; (void)value;
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@@ -657,6 +951,15 @@ void free_config(struct utun_config *config) {
// Free firewall rules
u_free(config->global.firewall_rules);
// Free control allow rules (array allocated with realloc)
u_free(config->global.control_allows);
// Free allowed keys
struct CFG_ALLOWED_KEY *ak = config->global.allowed_keys;
while (ak) {
struct CFG_ALLOWED_KEY *next = ak->next;
u_free(ak);
ak = next;
}
u_free(config);
}
@@ -678,20 +981,20 @@ void print_config(const struct utun_config *cfg) {
const struct global_config *g = &cfg->global;
char ip_buffer[64];
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d, net_debug=%d",
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d",
g->my_private_key_hex, g->my_public_key_hex,
(unsigned long long)g->my_node_id,
g->tun_ifname[0] ? g->tun_ifname : "auto",
ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)),
g->mtu, g->keepalive_timeout, g->tun_test_mode, g->net_debug);
g->mtu, g->keepalive_timeout, g->tun_test_mode);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
struct CFG_SERVER *s = cfg->servers;
while (s) {
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d, loss_rate=%d%%)",
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
s->so_mark, s->netif_index, s->type, s->mtu, s->loss_rate);
s->so_mark, s->netif_index, s->type, s->mtu);
s = s->next;
}
@@ -741,6 +1044,21 @@ void print_config(const struct utun_config *cfg) {
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Allowed keys: allow_all=%d, count=%d", g->allowed_keys_allow_all, g->allowed_keys_count);
if (!g->allowed_keys_allow_all && g->allowed_keys_count > 0) {
struct CFG_ALLOWED_KEY *ak = g->allowed_keys;
while (ak) {
char key_hex[SC_PUBKEY_SIZE * 2 + 1];
for (int i = 0; i < SC_PUBKEY_SIZE; i++) {
snprintf(key_hex + i * 2, sizeof(key_hex) - i * 2, "%02x", ak->key_bin[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " key=%s", key_hex);
ak = ak->next;
}
}
}
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
+64 -3
View File
@@ -5,6 +5,7 @@
#include <stdint.h>
#include <stddef.h>
#include "../lib/platform_compat.h"
#include "secure_channel.h"
#ifdef __cplusplus
extern "C" {
@@ -27,6 +28,10 @@ struct IP {
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PRIVATE 3
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
#define CFG_IPV6_MODE_PERMANENT 2 // permanent IPv6 from interface
struct CFG_SERVER {
struct CFG_SERVER* next;
char name[MAX_CONN_NAME_LEN];
@@ -35,8 +40,9 @@ struct CFG_SERVER {
int so_mark;
int fib; // FreeBSD FIB (routing table), -1 = don't set
uint8_t type; // public/nat/private
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
int mtu;
int loss_rate; // packet loss rate in percent (0-100), default 0
uint8_t only_local; // 1 = only local connections, no forwarding
};
struct CFG_CLIENT_LINK {
@@ -66,6 +72,23 @@ struct CFG_FIREWALL_RULE {
uint8_t bypass; // 1 for allow=all (bypass all checks)
};
struct CFG_CONTROL_ALLOW {
uint32_t network; // IPv4 network in host byte order
uint32_t netmask; // netmask in host byte order (e.g. 0xffffff00 for /24)
};
struct CFG_ALLOWED_KEY {
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
struct CFG_ALLOWED_KEY *next;
};
#define MAX_TCP_PROXY_MAPPINGS 32
struct tcp_proxy_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
};
struct global_config {
char name[16]; // Instance name
char my_private_key_hex[MAX_KEY_LEN];
@@ -76,8 +99,7 @@ struct global_config {
int mtu;
struct sockaddr_storage control_sock;
char control_ip[MAX_ADDR_LEN]; // Control server IP address
int net_debug;
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
char debug_level[16]; // debug level: error, warn, info, debug, trace
@@ -96,11 +118,50 @@ struct global_config {
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200)
int inflight_min_bytes; // inflight window starting size (default: 2000)
int inflight_max_bytes; // inflight window max cap (default: 100000)
// Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules;
int firewall_rule_count;
int firewall_bypass_all;
// Control server configuration ([control] section)
struct CFG_CONTROL_ALLOW *control_allows;
int control_allow_count;
// Allowed keys configuration ([allowed_keys] section)
struct CFG_ALLOWED_KEY *allowed_keys;
int allowed_keys_count;
int allowed_keys_allow_all;
// NAT configuration ([nat] section)
int nat_enabled;
char nat_tun_ifname[16];
struct IP nat_tun_ip;
uint64_t nat_via_node_id; // 0 = this node is provider; !=0 = client, use this provider
uint16_t nat_port_start;
uint16_t nat_port_end;
#define MAX_NAT_FORWARDS 64
struct {
uint8_t proto;
uint32_t internal_ip_host;
uint16_t internal_port_net;
uint16_t external_port;
} nat_forwards[MAX_NAT_FORWARDS];
int nat_forward_count;
// TCP proxy configuration ([tcp_proxy] section)
int tcp_proxy_enabled;
char tcp_proxy_tun_name[16];
char tcp_proxy_tun_ip[64];
int tcp_proxy_mtu;
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
int tcp_proxy_mapping_count;
// Remote proxy (exit node) configuration ([remote_proxy] section)
int remote_proxy_enabled;
};
struct utun_config {
+4 -4
View File
@@ -257,10 +257,10 @@ int config_ensure_keys_and_node_id(const char *filename) {
// Debug: print what we found
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Checking config - priv_key='%s' (len=%zu), pub_key='%s' (len=%zu), node_id=%016llx",
global->my_private_key_hex ? global->my_private_key_hex : "NULL",
global->my_private_key_hex ? strlen(global->my_private_key_hex) : 0,
global->my_public_key_hex ? global->my_public_key_hex : "NULL",
global->my_public_key_hex ? strlen(global->my_public_key_hex) : 0,
global->my_private_key_hex[0] ? global->my_private_key_hex : "NULL",
global->my_private_key_hex[0] ? strlen(global->my_private_key_hex) : 0,
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL",
global->my_public_key_hex[0] ? strlen(global->my_public_key_hex) : 0,
(unsigned long long)global->my_node_id);
+203 -10
View File
@@ -10,8 +10,12 @@
#include "etcp_connections.h"
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "pkt_normalizer.h"
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/u_async.h"
#include "../lib/timeout_heap.h"
#include "../lib/memory_pool.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
@@ -87,6 +91,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
static void close_client(struct control_server* server, struct control_client* client);
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
@@ -269,6 +274,24 @@ void control_server_shutdown(struct control_server* server) {
* Client Connection Handling
* ============================================================================ */
static int is_control_ip_allowed(const struct control_server* server, uint32_t client_ip) {
if (!server || !server->instance || !server->instance->config) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control IP check: no config available");
return 0;
}
const struct global_config *g = &server->instance->config->global;
if (g->control_allow_count == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied (no allow rules) - add control_allow=IP/mask to [control] in config");
return 0;
}
for (int i = 0; i < g->control_allow_count; i++) {
const struct CFG_CONTROL_ALLOW *r = &g->control_allows[i];
if ((client_ip & r->netmask) == r->network) return 1;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied from IP (not in allow list) - add control_allow=IP/mask to [control]");
return 0;
}
static void accept_callback(socket_t fd, void* arg) {
struct control_server* server = (struct control_server*)arg;
@@ -317,6 +340,28 @@ static void accept_callback(socket_t fd, void* arg) {
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
}
#endif
/* Check allowed IP (default deny all) */
uint32_t client_ip = 0;
if (client_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
client_ip = ntohl(sin->sin_addr.s_addr);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "IPv6 not supported for control allow list");
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
if (!is_control_ip_allowed(server, client_ip)) {
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Accept...");
/* Check max clients */
@@ -487,8 +532,11 @@ static void client_read_callback(socket_t fd, void* arg) {
}
client->recv_len += received;
/* Process messages */
if (client->recv_len > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow");
if (server) close_client(server, client);
return;
}
if (server) {
handle_client_data(server, client);
}
@@ -560,6 +608,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
while (client->recv_len >= sizeof(struct etcpmon_msg_header)) {
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer;
if (hdr->size == 0 || hdr->size > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message size from client: %u", hdr->size);
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_ACTION) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Validate header */
if (etcpmon_validate_header(hdr) != 0) {
if (server->log_file) {
@@ -605,6 +664,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client selected connection: %016llX",
(unsigned long long)cmd->peer_node_id);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SELECT_CONN payload size %u", payload_size);
close_client(server, client);
return;
}
break;
@@ -617,6 +680,22 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_LIST_SOCKETS:
send_socket_list(server, client, req_seq);
break;
case ETCPMON_CMD_ACTION:
if (payload_size >= sizeof(struct etcpmon_cmd_action)) {
struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload;
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 {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
}
}
break;
case ETCPMON_CMD_DISCONNECT:
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
@@ -627,14 +706,9 @@ static void handle_client_data(struct control_server* server, struct control_cli
return;
default:
if (server->log_file) {
fprintf(server->log_file, "%llu: [ERROR] Unknown command from client: 0x%02X\n",
(unsigned long long)get_timestamp_ms(), hdr->type);
fflush(server->log_file);
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
send_error(client, ETCPMON_ERR_INVALID_CMD, "Unknown command", req_seq);
break;
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Remove processed message from buffer */
@@ -705,6 +779,104 @@ static void send_conn_list(struct control_server* server, struct control_client*
u_free(buffer);
}
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct UTUN_INSTANCE* instance = server->instance;
if (!instance || !instance->etcp_sockets) {
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(0);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list),
ETCPMON_RSP_SOCKET_LIST, seq_id);
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
rsp->count = 0;
send(client->fd, buffer, rsp_size, 0);
u_free(buffer);
return;
}
uint8_t count = 0;
struct ETCP_SOCKET* sock = instance->etcp_sockets;
while (sock && count < ETCPMON_MAX_CONNECTIONS) {
count++;
sock = sock->next;
}
if (count > ETCPMON_MAX_CONNECTIONS) count = ETCPMON_MAX_CONNECTIONS;
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(count);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate socket list buffer");
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list) +
count * sizeof(struct etcpmon_socket_info),
ETCPMON_RSP_SOCKET_LIST,
seq_id);
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
rsp->count = count;
struct etcpmon_socket_info* info = (struct etcpmon_socket_info*)(buffer + sizeof(*hdr) + sizeof(*rsp));
sock = instance->etcp_sockets;
for (uint8_t i = 0; i < count && sock; i++) {
strncpy(info[i].name, sock->name, ETCPMON_MAX_CONN_NAME - 1);
info[i].name[ETCPMON_MAX_CONN_NAME - 1] = '\0';
info[i].is_ipv6 = (sock->interface_addr.ss_family == AF_INET6) ? 1 : 0;
if (sock->interface_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->interface_addr;
inet_ntop(AF_INET, &sin->sin_addr, info[i].ip, sizeof(info[i].ip));
info[i].port = ntohs(sin->sin_port);
} else if (sock->interface_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->interface_addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].ip, sizeof(info[i].ip));
info[i].port = ntohs(sin6->sin6_port);
} else {
strncpy(info[i].ip, "unknown", sizeof(info[i].ip) - 1);
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
info[i].port = 0;
}
info[i].nat_type = sock->nat_type;
/* NAT address from socket (set when INIT_RESPONSE received) */
info[i].nat_ip[0] = '\0';
info[i].nat_port = 0;
if (sock->nat_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->nat_addr;
inet_ntop(AF_INET, &sin->sin_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin->sin_port);
} else if (sock->nat_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->nat_addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin6->sin6_port);
}
sock = sock->next;
}
if (server->log_file) {
log_hex_data(server->log_file, "TX", buffer, rsp_size);
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
(unsigned long long)get_timestamp_ms(), count);
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
}
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct UTUN_INSTANCE* instance = server->instance;
@@ -823,10 +995,21 @@ static void send_metrics(struct control_server* server, struct control_client* c
rsp->etcp.cnt_ack_hit_sndq = conn->cnt_ack_hit_sndq;
rsp->etcp.cnt_ack_miss = conn->cnt_ack_miss;
rsp->etcp.cnt_link_wait = conn->cnt_link_wait;
rsp->etcp.rx_dup_count = conn->rx_dup_count;
rsp->etcp.tx_dup_count = conn->tx_dup_count;
rsp->etcp.tx_state = conn->tx_state;
for (int i = 0; i < 8; i++) {
rsp->etcp.debug[i] = conn->debug[i];
}
/* System resources */
if (instance->ua && instance->ua->timeout_heap) {
rsp->etcp.active_timeouts = (uint32_t)timeout_heap_get_size(instance->ua->timeout_heap);
} else {
rsp->etcp.active_timeouts = 0;
}
rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks());
/* Fill TUN metrics */
if (instance->tun) {
@@ -857,6 +1040,15 @@ static void send_metrics(struct control_server* server, struct control_client* c
rsp->tun.rt_local = 0;
rsp->tun.rt_learned = 0;
}
/* BGP routing */
if (instance->bgp) {
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
} else {
rsp->tun.rt_bgp_senders = 0;
rsp->tun.rt_bgp_nodes = 0;
}
} else {
memset(&rsp->tun, 0, sizeof(rsp->tun));
}
@@ -885,6 +1077,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].keepalive_recv = (uint32_t)link->keepalive_recv_count;
link_info[i].inflight_bytes = link->inflight_bytes;
link_info[i].inflight_packets = link->inflight_packets;
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
link = link->next;
}
+88 -7
View File
@@ -10,6 +10,7 @@
#include "../lib/socket_compat.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include "etcp_connections.h"
#include <stdlib.h>
#include <string.h>
@@ -130,7 +131,6 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
}
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data;
ssize_t n = socket_sendto(dn->sock, pkt->data, pkt->len,
(struct sockaddr*)&dir->dest_addr, dir->dest_len);
if (n < 0) {
@@ -149,7 +149,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
int delay_tb = 0;
if (dir->bandwidth_kbps > 0) {
uint64_t delay_calc = (uint64_t)bytes_sent * 80000ULL / (uint64_t)dir->bandwidth_kbps;
uint64_t delay_calc = (uint64_t)bytes_sent * 80ULL / (uint64_t)dir->bandwidth_kbps;
delay_tb = (int)delay_calc;
if (delay_tb == 0) delay_tb = 1;
}
@@ -158,7 +158,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
if (sarg) {
sarg->dn = dn;
sarg->dir_idx = dir_idx;
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback);
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper");
if (dir->shaper_timer) {
dir->shaper_timer_set++;
} else {
@@ -270,7 +270,7 @@ static void dummynet_read_callback(socket_t sock, void* user_arg) {
targ->dn = dn;
targ->entry = entry;
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback);
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback, "dummynet_delay");
if (!timer) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to set delay timer");
dir->stats.dropped++;
@@ -304,7 +304,7 @@ static void dummynet_delay_callback(void* user_arg) {
/* Добавляем в очередь */
uint32_t id = (uint32_t)(uintptr_t)entry;
if (queue_data_put(dir->queue, entry, id) != 0) {
if (queue_data_put(dir->queue, entry) != 0) {
dir->stats.dropped++;
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx);
queue_entry_free(entry);
@@ -326,7 +326,15 @@ static void dummynet_delay_callback(void* user_arg) {
if (sarg) {
sarg->dn = dn;
sarg->dir_idx = dir_idx;
dir->shaper_timer = uasync_set_timeout(dn->ua, 0, sarg, dummynet_shaper_callback);
int shaper_delay_tb = 0;
if (dir->bandwidth_kbps > 0) {
uint64_t delay_calc = (uint64_t)pkt->len * 80ULL / (uint64_t)dir->bandwidth_kbps;
shaper_delay_tb = (int)delay_calc;
if (shaper_delay_tb == 0) shaper_delay_tb = 1;
}
dir->shaper_timer = uasync_set_timeout(dn->ua, shaper_delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper0");
if (dir->shaper_timer) dir->shaper_timer_set++;
else { u_free(sarg); dir->shaper_pending = 0; }
} else {
dir->shaper_pending = 0;
}
@@ -359,7 +367,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
/* Создаём очереди для направлений */
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) {
dn->dirs[i].queue = queue_new(ua, 0, "dummynet1");
dn->dirs[i].queue = queue_new(ua, 0, 0, 0, "dummynet1");
if (!dn->dirs[i].queue) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create queue for direction %d", i);
dummynet_destroy(dn);
@@ -562,3 +570,76 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
*shaper_set = dir->shaper_timer_set;
*shaper_fire = dir->shaper_timer_fire;
}
// === Inline filter: send_hook для etcp_udp_send ===
struct dummynet_filter {
struct UASYNC* ua;
uint32_t loss_permille;
uint32_t delay_ms;
uint32_t jitter_ms;
struct ETCP_LINK* link;
struct dummynet_stats stats;
};
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* ctx) {
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
(void)link;
df->stats.recv++;
if (df->loss_permille > 0) {
uint32_t roll = (uint32_t)rand() % 1000;
if (roll < df->loss_permille) {
df->stats.lost++;
return (ssize_t)len;
}
}
ssize_t sent = socket_sendto(fd, buf, len, addr, addr_len);
if (sent > 0) df->stats.sent++;
return sent;
}
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua) {
struct dummynet_filter* df = u_calloc(1, sizeof(*df));
if (!df) return NULL;
df->ua = ua;
return df;
}
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille) {
if (!df) return;
if (loss_permille > 1000) loss_permille = 1000;
df->loss_permille = loss_permille;
}
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms) {
if (!df) return;
df->delay_ms = delay_ms;
df->jitter_ms = jitter_ms;
}
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link) {
if (!df || !link) return;
dummynet_filter_detach(df);
df->link = link;
link->send_hook = dummynet_filter_send_hook;
link->send_hook_ctx = df;
}
void dummynet_filter_detach(struct dummynet_filter* df) {
if (!df || !df->link) return;
df->link->send_hook = NULL;
df->link->send_hook_ctx = NULL;
df->link = NULL;
}
void dummynet_filter_destroy(struct dummynet_filter* df) {
if (!df) return;
dummynet_filter_detach(df);
u_free(df);
}
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
return df ? &df->stats : NULL;
}
+14
View File
@@ -163,4 +163,18 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
uint64_t *delay_set, uint64_t *delay_fire,
uint64_t *shaper_set, uint64_t *shaper_fire);
// === Inline filter: встраивается в etcp_udp_send через link->send_hook ===
// Не создаёт свой сокет — перехватывает пакеты напрямую из пути отправки.
struct ETCP_LINK;
struct dummynet_filter;
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua);
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille);
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms);
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link);
void dummynet_filter_detach(struct dummynet_filter* df);
void dummynet_filter_destroy(struct dummynet_filter* df);
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df);
#endif // DUMMYNET_H
+306
View File
@@ -0,0 +1,306 @@
#include "eim_nat.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <string.h>
#include <stdlib.h>
// IP header offsets
#define IP_IHL_OFFSET 0
#define IP_PROTO_OFFSET 9
#define IP_CHECKSUM_OFFSET 10
#define IP_SRC_ADDR_OFFSET 12
#define IP_DST_ADDR_OFFSET 16
#define IP_HDR_MIN_SIZE 20
#define IP_FRAG_MF_MASK 0x2000
#define IP_FRAG_OFF_MASK 0x1FFF
#define TCP_SRC_PORT_OFFSET 0
#define TCP_DST_PORT_OFFSET 2
#define TCP_CHECKSUM_OFFSET 16
#define UDP_SRC_PORT_OFFSET 0
#define UDP_DST_PORT_OFFSET 2
#define UDP_CHECKSUM_OFFSET 6
#define ICMP_TYPE_OFFSET 0
#define ICMP_ID_OFFSET 4
#define ICMP_CHECKSUM_OFFSET 2
#define ICMP_ECHO_REQUEST 8
#define ICMP_ECHO_REPLY 0
static ip_str_t ip_host_to_str(uint32_t ip_host) {
struct in_addr a; a.s_addr = htonl(ip_host);
return ip_to_str(&a, AF_INET);
}
static uint16_t csum_update_n(uint16_t old_csum, const uint16_t* old_vals,
const uint16_t* new_vals, int n) {
uint32_t sum = (uint32_t)(uint16_t)(~old_csum);
for (int i = 0; i < n; i++) { sum -= old_vals[i]; sum += new_vals[i]; }
sum = (sum & 0xFFFF) + (sum >> 16); sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)(~sum);
}
static void csum_update_ip(uint8_t* ip_data, uint32_t old_ip_host, uint32_t new_ip_host) {
uint16_t old_csum; memcpy(&old_csum, ip_data + IP_CHECKSUM_OFFSET, 2);
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
uint16_t old_w[2] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16)};
uint16_t new_w[2] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16)};
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 2);
memcpy(ip_data + IP_CHECKSUM_OFFSET, &new_csum, 2);
}
static void csum_update_transport(uint8_t* transport, int csum_off,
uint32_t old_ip_host, uint32_t new_ip_host,
uint16_t old_port_net, uint16_t new_port_net) {
uint16_t old_csum; memcpy(&old_csum, transport + csum_off, 2);
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
uint16_t old_w[3] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16), old_port_net};
uint16_t new_w[3] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16), new_port_net};
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 3);
memcpy(transport + csum_off, &new_csum, 2);
}
static void csum_update_icmp(uint8_t* icmp, uint16_t old_word, uint16_t new_word) {
uint16_t old_csum; memcpy(&old_csum, icmp + ICMP_CHECKSUM_OFFSET, 2);
uint16_t new_csum = csum_update_n(old_csum, &old_word, &new_word, 1);
memcpy(icmp + ICMP_CHECKSUM_OFFSET, &new_csum, 2);
}
static struct eim_nat_entry* eim_nat_find_egress(struct eim_nat_ctx* ctx,
uint8_t proto, uint32_t ip_host, uint16_t port_net) {
for (uint16_t i = ctx->port_start; i <= ctx->port_end; i++) {
struct eim_nat_entry* e = &ctx->table[i];
if (e->state != EIM_NAT_ENTRY_FREE && e->proto == proto &&
e->internal_ip == ip_host && e->internal_port == port_net) return e;
}
return NULL;
}
static uint16_t eim_nat_alloc_port(struct eim_nat_ctx* ctx) {
uint16_t start = ctx->next_port;
do {
if (ctx->table[ctx->next_port].state == EIM_NAT_ENTRY_FREE) {
uint16_t port = ctx->next_port;
ctx->next_port++;
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
return port;
}
ctx->next_port++;
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
} while (ctx->next_port != start);
return 0;
}
// ==================== Public API ====================
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
uint64_t src_node_id, struct ETCP_CONN* src_conn) {
if (ip_len < IP_HDR_MIN_SIZE) return -1;
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
if (ihl < 5) return -1;
uint16_t ip_hdr_len = ihl * 4;
if (ip_len < ip_hdr_len) return -1;
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
uint8_t proto = ip_data[IP_PROTO_OFFSET];
uint32_t src_ip_net, src_ip_host;
memcpy(&src_ip_net, ip_data + IP_SRC_ADDR_OFFSET, 4);
src_ip_host = ntohl(src_ip_net);
uint8_t* transport = ip_data + ip_hdr_len;
size_t tlen = ip_len - ip_hdr_len;
uint16_t src_port_net = 0;
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
int is_icmp_echo = 0;
if (is_tcp) memcpy(&src_port_net, transport + TCP_SRC_PORT_OFFSET, 2);
else if (is_udp) memcpy(&src_port_net, transport + UDP_SRC_PORT_OFFSET, 2);
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
is_icmp_echo = 1; memcpy(&src_port_net, transport + ICMP_ID_OFFSET, 2);
}
}
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net);
uint16_t ext_port_host;
if (entry) {
ext_port_host = (uint16_t)(entry - ctx->table);
} else {
ext_port_host = eim_nat_alloc_port(ctx);
if (ext_port_host == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT port range exhausted (%s:%u proto=%u)",
ip_host_to_str(src_ip_host).str, ntohs(src_port_net), proto);
return -1;
}
entry = &ctx->table[ext_port_host];
entry->internal_ip = src_ip_host;
entry->internal_port = src_port_net;
entry->proto = proto;
entry->state = EIM_NAT_ENTRY_ACTIVE;
entry->src_node_id = src_node_id;
entry->src_conn = src_conn;
entry->last_seen = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT map: %s:%u -> %s:%u (proto=%u) from node %016llx",
ip_host_to_str(src_ip_host).str, ntohs(src_port_net),
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
(unsigned long long)src_node_id);
}
uint32_t gw_ip_net = htonl(ctx->gateway_ip);
memcpy(ip_data + IP_SRC_ADDR_OFFSET, &gw_ip_net, 4);
csum_update_ip(ip_data, src_ip_host, ctx->gateway_ip);
uint16_t new_port_net = htons(ext_port_host);
if (is_tcp) {
memcpy(transport + TCP_SRC_PORT_OFFSET, &new_port_net, 2);
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
} else if (is_udp) {
memcpy(transport + UDP_SRC_PORT_OFFSET, &new_port_net, 2);
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
} else if (is_icmp_echo) {
memcpy(transport + ICMP_ID_OFFSET, &new_port_net, 2);
csum_update_icmp(transport, src_port_net, new_port_net);
}
return 0;
}
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
struct eim_nat_entry** out_entry) {
if (ip_len < IP_HDR_MIN_SIZE) return -1;
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
if (ihl < 5) return -1;
uint16_t ip_hdr_len = ihl * 4;
if (ip_len < ip_hdr_len) return -1;
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
uint8_t proto = ip_data[IP_PROTO_OFFSET];
uint32_t dst_ip_net;
memcpy(&dst_ip_net, ip_data + IP_DST_ADDR_OFFSET, 4);
if (dst_ip_net != htonl(ctx->gateway_ip)) return 0;
uint8_t* transport = ip_data + ip_hdr_len;
size_t tlen = ip_len - ip_hdr_len;
uint16_t dst_port_net = 0;
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
int is_icmp_echo = 0;
if (is_tcp) memcpy(&dst_port_net, transport + TCP_DST_PORT_OFFSET, 2);
else if (is_udp) memcpy(&dst_port_net, transport + UDP_DST_PORT_OFFSET, 2);
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
is_icmp_echo = 1; memcpy(&dst_port_net, transport + ICMP_ID_OFFSET, 2);
}
}
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
uint16_t ext_port_host = ntohs(dst_port_net);
if (ext_port_host < ctx->port_start || ext_port_host > ctx->port_end) return 0;
struct eim_nat_entry* entry = &ctx->table[ext_port_host];
if (entry->state == EIM_NAT_ENTRY_FREE) return 0;
if (entry->proto != proto) return 0;
uint32_t dst_ip_host = ntohl(dst_ip_net);
uint32_t internal_ip_net = htonl(entry->internal_ip);
memcpy(ip_data + IP_DST_ADDR_OFFSET, &internal_ip_net, 4);
csum_update_ip(ip_data, dst_ip_host, entry->internal_ip);
uint16_t internal_port_net = entry->internal_port;
if (is_tcp) {
memcpy(transport + TCP_DST_PORT_OFFSET, &internal_port_net, 2);
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
} else if (is_udp) {
memcpy(transport + UDP_DST_PORT_OFFSET, &internal_port_net, 2);
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
} else if (is_icmp_echo) {
memcpy(transport + ICMP_ID_OFFSET, &internal_port_net, 2);
csum_update_icmp(transport, dst_port_net, internal_port_net);
}
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT unmap: %s:%u <- %s:%u (proto=%u) back to node %016llx",
ip_host_to_str(entry->internal_ip).str, ntohs(entry->internal_port),
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
(unsigned long long)entry->src_node_id);
if (out_entry) *out_entry = entry;
return 0;
}
// ==================== Init / Destroy ====================
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g) {
if (!ctx || !g) return -1;
memset(ctx, 0, sizeof(*ctx));
if (!g->nat_enabled) return 0;
if (g->nat_tun_ip.family == AF_INET) {
ctx->gateway_ip = ntohl(g->nat_tun_ip.addr.v4.s_addr);
} else if (g->tun_ip.family == AF_INET) {
ctx->gateway_ip = ntohl(g->tun_ip.addr.v4.s_addr);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No valid IP for NAT gateway");
return -1;
}
ctx->port_start = g->nat_port_start;
ctx->port_end = g->nat_port_end;
if (ctx->port_start == 0 || ctx->port_end == 0 || ctx->port_start >= ctx->port_end) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Invalid NAT port range: %u-%u", ctx->port_start, ctx->port_end);
return -1;
}
ctx->next_port = ctx->port_start;
ctx->table_size = EIM_NAT_TABLE_SIZE;
ctx->table = u_calloc(ctx->table_size, sizeof(struct eim_nat_entry));
if (!ctx->table) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to allocate NAT table"); return -1; }
for (int i = 0; i < g->nat_forward_count; i++) {
uint16_t ext = g->nat_forwards[i].external_port;
if (ext >= ctx->table_size) continue;
struct eim_nat_entry* e = &ctx->table[ext];
e->internal_ip = g->nat_forwards[i].internal_ip_host;
e->internal_port = g->nat_forwards[i].internal_port_net;
e->proto = g->nat_forwards[i].proto;
e->state = EIM_NAT_ENTRY_STATIC;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
ip_host_to_str(e->internal_ip).str, ntohs(e->internal_port), ext, e->proto);
}
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine initialized: gw=%s ports=%u-%u",
ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
return 0;
}
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx) {
if (!ctx || !ctx->initialized) return;
u_free(ctx->table);
memset(ctx, 0, sizeof(*ctx));
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine destroyed");
}
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
uint32_t internal_ip_host, uint16_t internal_port_net,
uint16_t external_port) {
if (!ctx || !ctx->initialized || external_port >= ctx->table_size) return -1;
if (ctx->table[external_port].state != EIM_NAT_ENTRY_FREE) return -1;
struct eim_nat_entry* e = &ctx->table[external_port];
e->internal_ip = internal_ip_host;
e->internal_port = internal_port_net;
e->proto = proto;
e->state = EIM_NAT_ENTRY_STATIC;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
ip_host_to_str(internal_ip_host).str, ntohs(internal_port_net), external_port, proto);
return 0;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef EIM_NAT_H
#define EIM_NAT_H
#include <stdint.h>
#include <stddef.h>
struct ETCP_CONN;
#define EIM_NAT_TABLE_SIZE 65536
#define EIM_NAT_ENTRY_FREE 0
#define EIM_NAT_ENTRY_ACTIVE 1
#define EIM_NAT_ENTRY_STATIC 2
#define IPPROTO_TCP_UINT8 6
#define IPPROTO_UDP_UINT8 17
#define IPPROTO_ICMP_UINT8 1
struct eim_nat_entry {
uint32_t internal_ip;
uint16_t internal_port;
uint8_t proto;
uint8_t state;
uint64_t src_node_id; // which node sent the original packet
uint64_t last_seen;
struct ETCP_CONN* src_conn; // cached connection for sendback
};
// Pure NAT engine state (no transport/TUN/ETCP fields)
struct eim_nat_ctx {
uint32_t gateway_ip;
uint16_t port_start;
uint16_t port_end;
uint16_t next_port;
struct eim_nat_entry* table;
size_t table_size;
int initialized;
};
struct global_config;
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g);
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx);
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
uint64_t src_node_id, struct ETCP_CONN* src_conn);
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
struct eim_nat_entry** out_entry);
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
uint32_t internal_ip_host, uint16_t internal_port_net,
uint16_t external_port);
#endif
+486 -87
View File
@@ -5,6 +5,7 @@
#include "etcp_loadbalancer.h"
#include "routing.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
@@ -58,6 +59,116 @@ static void drain_and_free_queue(struct ll_queue** q) {
*q = NULL;
}
static int inflight_seq_cmp(const void* a, const void* b) {
const struct INFLIGHT_PACKET* pa = *(const struct INFLIGHT_PACKET**)a;
const struct INFLIGHT_PACKET* pb = *(const struct INFLIGHT_PACKET**)b;
if (pa->seq < pb->seq) return -1;
if (pa->seq > pb->seq) return 1;
return 0;
}
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,
uint32_t* returned) {
uint32_t need = (uint32_t)*asm_len + dgram_len;
if (need >= ASM_BUF_MAX_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] assembly buffer would exceed max size %d", etcp->log_name, ASM_BUF_MAX_SIZE);
return;
}
if (need > *asm_cap) {
uint16_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_buf) return;
*asm_buf = new_buf;
*asm_cap = new_cap;
}
memcpy(*asm_buf + *asm_len, dgram, dgram_len);
*asm_len = need;
while (*asm_len >= 2) {
uint16_t pkt_len = (*asm_buf)[0] | ((*asm_buf)[1] << 8);
if (pkt_len == 0 || pkt_len > 16384 || *asm_len < 2 + pkt_len) break;
const uint8_t* pkt_data = *asm_buf + 2;
if (pkt_data[0] != ETCP_ID_ROUTE_ENTRY) {
struct ll_entry* e = ll_alloc_lldgram(pkt_len);
if (e) {
memcpy(e->dgram, pkt_data, pkt_len);
e->len = pkt_len;
queue_data_put(pn->input, e);
(*returned)++;
}
}
uint16_t consumed = 2 + pkt_len;
*asm_len -= consumed;
if (*asm_len > 0) memmove(*asm_buf, *asm_buf + consumed, *asm_len);
}
}
static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) {
struct PKTNORM* pn = etcp->normalizer;
if (!pn) return;
struct INFLIGHT_PACKET** inflight = NULL;
int inflight_count = 0;
struct ll_entry** input_frags = NULL;
int input_count = 0;
struct ll_entry* entry;
while ((entry = queue_data_get(etcp->input_wait_ack))) {
inflight = u_realloc(inflight, (inflight_count + 1) * sizeof(*inflight));
inflight[inflight_count++] = (struct INFLIGHT_PACKET*)entry;
}
while ((entry = queue_data_get(etcp->input_send_q))) {
inflight = u_realloc(inflight, (inflight_count + 1) * sizeof(*inflight));
inflight[inflight_count++] = (struct INFLIGHT_PACKET*)entry;
}
while ((entry = queue_data_get(etcp->input_queue))) {
input_frags = u_realloc(input_frags, (input_count + 1) * sizeof(*input_frags));
input_frags[input_count++] = entry;
}
if (inflight_count + input_count == 0 && (!pn->data || pn->data_ptr == 0)) return;
if (inflight_count > 0) qsort(inflight, inflight_count, sizeof(*inflight), inflight_seq_cmp);
uint8_t* asm_buf = NULL;
uint16_t asm_len = 0;
uint16_t asm_cap = 0;
uint32_t returned_packets = 0;
for (int i = 0; i < inflight_count; i++) {
feed_dgram_to_asm(etcp, pn, inflight[i]->ll.dgram, inflight[i]->ll.len,
&asm_buf, &asm_len, &asm_cap, &returned_packets);
memory_pool_free(etcp->instance->data_pool, inflight[i]->ll.dgram);
memory_pool_free(etcp->inflight_pool, inflight[i]);
}
u_free(inflight);
for (int i = 0; i < input_count; i++) {
feed_dgram_to_asm(etcp, pn, input_frags[i]->dgram, input_frags[i]->len,
&asm_buf, &asm_len, &asm_cap, &returned_packets);
memory_pool_free(etcp->instance->data_pool, input_frags[i]->dgram);
memory_pool_free(etcp->io_pool, input_frags[i]);
}
u_free(input_frags);
if (pn->data && pn->data_ptr > 0) {
feed_dgram_to_asm(etcp, pn, pn->data, pn->data_ptr,
&asm_buf, &asm_len, &asm_cap, &returned_packets);
}
if (asm_buf) { u_free(asm_buf); }
if (returned_packets > 0) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] returned %u packets to normalizer input during reinit",
etcp->log_name, returned_packets);
}
}
uint16_t get_current_timestamp() {
return (uint16_t)get_time_tb();
}
@@ -86,17 +197,18 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
}
etcp->instance = instance;
etcp->input_queue = queue_new(instance->ua, 0, "ETCP input"); // No hash for input_queue
etcp->output_queue = queue_new(instance->ua, 0, "ETCP output"); // No hash for output_queue
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "input_send_q"); // Hash for send_q
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "input_wait_ack"); // Hash for wait_ack
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "recv_q"); // Hash for send_q
etcp->ack_q = queue_new(instance->ua, 0, "ack_q");
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_send_q"); // Hash for send_q
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "recv_q"); // Hash for recv_q
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
etcp->optimal_inflight=10000;
etcp->optimal_inflight=100000;
etcp->initialized=0;
etcp->links_up=0;
etcp->last_rr_link=NULL;
etcp->name = u_strdup(name);
// Initialize log_name with local node_id (peer will be updated later when known)
snprintf(etcp->log_name, sizeof(etcp->log_name), "%04X->???? [%s]", (uint16_t)instance->node_id, etcp->name);
@@ -111,7 +223,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
// etcp->window_size = MAX_INFLIGHT_BYTES; // Not used
etcp->mtu = 1500; // Default MTU
etcp->mtu = ETCP_RFC791_MIN_MTU; // Default MTU per RFC 791
etcp->next_tx_id = 1;
etcp->rtt_avg_10 = 10; // Initial guess (1ms)
etcp->rtt_avg_100 = 10;
@@ -129,7 +241,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
queue_set_callback(etcp->input_wait_ack, wait_ack_cb, etcp);
queue_set_callback(etcp->ack_q, send_ack_req_cb, etcp);
// queue_set_callback(etcp->ack_q, send_ack_req_cb, etcp);// отправляем через таймаут
etcp->link_ready_for_send_fn = etcp_link_ready_callback;
@@ -146,10 +258,13 @@ 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);
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);
if (etcp->down_cbk) etcp->down_cbk(etcp, etcp->down_arg);
}
@@ -237,7 +352,7 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
// Reset connection
void etcp_conn_reset(struct ETCP_CONN* etcp) {
// Reset IDs
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Resetting ETCP instance [%d]", etcp->log_name);
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Resetting ETCP instance [%d]", etcp->log_name);
etcp->next_tx_id = 1;
etcp->last_rx_id = 0;
etcp->last_delivered_id = 0;
@@ -255,10 +370,16 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
etcp->bytes_sent_total = 0;
etcp->retransmissions_count = 0;
etcp->ack_packets_count = 0;
etcp->rx_dup_count = 0;
etcp->tx_dup_count = 0;
// Reset RTT history
memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history));
etcp->rtt_history_idx = 0;
etcp->last_rr_link = NULL;
// Return unconfirmed inflight data to normalizer input (до нормалайзера)
if (etcp->normalizer) etcp_return_inflight_to_normalizer(etcp);
// Clear queues (keep queue structures)
clear_queue(etcp->input_queue);
@@ -282,9 +403,15 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
l = l->next;
}
// Reset timers (just clear the pointers - timers will expire naturally)
etcp->retrans_timer = NULL;
etcp->ack_resp_timer = NULL;
// Cancel active timers to prevent memory leaks
if (etcp->retrans_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer);
etcp->retrans_timer = NULL;
}
if (etcp->ack_resp_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->ack_resp_timer);
etcp->ack_resp_timer = NULL;
}
etcp->reset_count++;
@@ -347,6 +474,19 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме
etcp->initialized = 0;// еще раз придёт conn_ready_callback
// Отменяем висящие NAT-ping'и для этого соединения
if (etcp->instance && etcp->instance->bgp) {
route_ping_cancel_for_conn(etcp->instance->bgp, etcp);
}
// Сбрасываем NAT-статус у всех линков этого соединения
struct ETCP_LINK* l = etcp->links;
while (l) {
l->nat_type = NAT_TYPE_UNKNOWN;
l->nat_check_status = NAT_CHECK_NONE;
l = l->next;
}
// Вызываем etcp_conn_reset для сброса состояния
etcp_conn_reset(etcp);
@@ -356,12 +496,17 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме
// внутренняя функция. Вызывается один раз когда первый линк готов.
void etcp_conn_ready(struct ETCP_CONN* conn) {
if (!conn) return;
conn->initialized = 1;
// Если tx_state не установлен (первое подключение без reinit), установим его
if (conn->tx_state == 0) {
conn->tx_state = ETCP_TX_STATE_DATA_WAIT;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Connection ready", conn->log_name);
// Вызываем callback если установлен
if (conn->ready_cbk) conn->ready_cbk(conn, conn->ready_arg);
if (conn->links_up) etcp_on_up(conn);
}
@@ -395,8 +540,8 @@ int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len) {
}
// Check length against maximum packet size
if (len > PACKET_DATA_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] packet too large (len=%zu, max=%d)", etcp->log_name, len, PACKET_DATA_SIZE);
if (len > ETCP_MAX_PAYLOAD_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] packet too large (len=%zu, max=%d)", etcp->log_name, len, ETCP_MAX_PAYLOAD_SIZE);
return -1;
}
@@ -447,8 +592,9 @@ static void input_queue_try_push(struct ETCP_CONN* etcp) {// пробуем пр
// когда очередь отправки пуста - пробуем взять новый пакет на обработку
size_t wait_ack_bytes = queue_total_bytes(etcp->input_wait_ack);
if (wait_ack_bytes <= etcp->optimal_inflight) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes);
queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes);
if (etcp->input_queue->count > 0) input_queue_cb(etcp->input_queue, etcp);
else queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет
}
}
@@ -463,7 +609,7 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
// когда очередь отправки пуста - пробуем взять новый пакет на обработку
size_t wait_ack_bytes = queue_total_bytes(etcp->input_wait_ack);
if (wait_ack_bytes <= etcp->optimal_inflight) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes);
queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет
}
}
@@ -501,6 +647,8 @@ void etcp_stats(struct ETCP_CONN* etcp) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] retransmissions_count: %u", etcp->log_name, etcp->retransmissions_count);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] ack_packets_count: %u", etcp->log_name, etcp->ack_packets_count);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unacked_bytes: %u", etcp->log_name, etcp->unacked_bytes);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] rx_dup_count: %u", etcp->log_name, etcp->rx_dup_count);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] tx_dup_count: %u", etcp->log_name, etcp->tx_dup_count);
// IDs
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] IDs:", etcp->log_name);
@@ -530,7 +678,8 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
struct INFLIGHT_PACKET* p = (struct INFLIGHT_PACKET*)queue_entry_new_from_pool(etcp->inflight_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] cannot allocate INFLIGHT_PACKET (pool=%p etcp=%p)", etcp->log_name, etcp->inflight_pool, etcp);
queue_entry_free((struct ll_entry*)in_pkt); // Free the ETCP_FRAGMENT
queue_dgram_free(&in_pkt->ll);
queue_entry_free((struct ll_entry*)in_pkt);
queue_resume_callback(q);
return;
}
@@ -547,11 +696,11 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
// memory_pool_free(etcp->io_pool, in_pkt);// перемещаем из io_pool в inflight_pool
queue_entry_free(&in_pkt->ll);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] input -> inflight (seq=%u, len=%u)", etcp->log_name, p->seq, p->ll.len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] TX input -> inflight (seq=%u, len=%u); Qlen: in=%d snd=%d wait=%d", etcp->log_name, p->seq, p->ll.len, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
int len=p->ll.len;// сохраним len
// Add to send queue
if (queue_data_put_with_index(etcp->input_send_q, &p->ll, 0, 4) != 0) {
if (queue_data_put_with_index(etcp->input_send_q, &p->ll) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet seq=%u to input_send_q", etcp->log_name, p->seq);
queue_dgram_free(&p->ll);
queue_entry_free(&p->ll);
@@ -563,19 +712,17 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_queue_cb: successfully moved from input_queue to input_send_q");
// etcp_conn_process_send_queue(etcp);// сразу обработаем этот пакет
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] nextloop, input_queue size=%d ", etcp->log_name, q->count);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] nextloop, input_queue size=%d ", etcp->log_name, q->count);
}
static void ack_timeout_cb(void* arg);
static void ack_timeout_check(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
uint64_t now = get_time_tb();
int32_t timeout = (etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2) / 16;
if (timeout<50) timeout=50;
if (timeout>10000) timeout=10000;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "starting check, now=%llu, timeout=%llu, rtt_avg_10=%u, jitter=%u",
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "starting check, now=%llu, timeout=%llu, rtt_avg_10=%u, jitter=%u",
(unsigned long long)now, (unsigned long long)timeout, etcp->rtt_avg_10, etcp->jitter);
@@ -586,20 +733,19 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
int64_t elapsed = now - pkt->last_timestamp;
if (elapsed > timeout) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] timeout for seq=%u, elapsed=%lld, now=%llu, timeout=%llu, send_count=%u. Moving: wait_ack -> send_q",
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] timeout for seq=%u, elapsed=%lld, now=%llu, timeout=%llu, send_count=%u. Moving: wait_ack -> send_q",
etcp->log_name, pkt->seq, (unsigned long long)elapsed, (unsigned long long)now, (unsigned long long)timeout, pkt->send_count);
// Remove from wait_ack
pkt=(struct INFLIGHT_PACKET*)queue_data_get(etcp->input_wait_ack);
if (!pkt) break;
// Increment counters
pkt->send_count++;
pkt->retrans_req_count++; // Optional, if used for retrans request logic
pkt->last_timestamp = now;
// Change state and add to send_q for retransmission
pkt->state = INFLIGHT_STATE_WAIT_SEND;
queue_data_put_with_index(etcp->input_send_q, (struct ll_entry*)pkt, 0, 4);
queue_data_put_with_index(etcp->input_send_q, (struct ll_entry*)pkt);
// Update stats
etcp->retransmissions_count++;
@@ -608,8 +754,8 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
// shedule timer
int64_t next_timeout=timeout - elapsed;
if (next_timeout<0) next_timeout=0;
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
return;
}
current = etcp->input_wait_ack->head;
@@ -634,16 +780,21 @@ static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
etcp_conn_process_send_queue(etcp);
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) queue_resume_callback(etcp->input_send_q);
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) {
queue_resume_callback(etcp->input_send_q);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q");
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy");
}
/*
static void send_ack_req_cb(struct ll_queue* q, void* arg) {// etcp->ack_q data ready
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] processing", etcp->log_name);
etcp_conn_process_send_queue(etcp);
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) queue_resume_callback(etcp->ack_q);
}
*/
void etcp_on_link_down(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
int up=0;
@@ -655,10 +806,22 @@ void etcp_on_link_down(struct ETCP_CONN* etcp) {
int was_up = etcp->links_up;
etcp->links_up = up;
if (up == 0 && was_up != 0) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "All links fall down");
etcp_on_down(etcp);
}
}
static void ack_response_timer_cb(void* arg) {// проверяем неотправленные ack response и отправляем если надо.
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
etcp->ack_resp_timer=NULL;
if (etcp->ack_q->count==0) return;// нечего отправлять
etcp_conn_process_send_queue(etcp);// проталкиваем (она же должна отправлять только ack если больше ничего нет)
// если ack все еще заняты - обновляем таймаут
if (etcp->ack_q->count) etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
// else etcp->ack_resp_timer=NULL;
}
static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
if (!etcp) return;
@@ -678,16 +841,13 @@ static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return;
etcp->tx_state = ETCP_TX_STATE_DATA_WAIT;
queue_resume_callback(etcp->input_send_q);
queue_resume_callback(etcp->ack_q);
}
// queue_resume_callback(etcp->ack_q);
if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) {
ack_response_timer_cb(etcp);
}
static void ack_response_timer_cb(void* arg) {// проверяем неотправленные ack response и отправляем если надо.
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
etcp_conn_process_send_queue(etcp);// проталкиваем (она же должна отправлять только ack если больше ничего нет)
// если ack все еще заняты - обновляем таймаут
if (etcp->ack_q->count) etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb);
else etcp->ack_resp_timer=NULL;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
}
// Process packets in send queue and transmit them
@@ -695,7 +855,13 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_DGRAM* dgram;
if (etcp->tx_state!=ETCP_TX_STATE_DATA_WAIT) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send)", etcp->log_name, etcp->tx_state);
char l_status[256]={0};
struct ETCP_LINK* link = etcp->links;
while (link) {
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s,%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy", link->send_blocked_inflight?"inf_block":"rdy");
link = link->next;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send) %s", etcp->log_name, etcp->tx_state, l_status);
return;
}
dgram = etcp_request_pkt(etcp);
@@ -703,17 +869,25 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
etcp_loadbalancer_send(dgram);
dgram = etcp_request_pkt(etcp);
}
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) {
queue_resume_callback(etcp->input_send_q);
}
}
// Подготовить и отправить кодограмму
// вызывается линком когда освобождается или очередью если появляются данные на передачу
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
struct ETCP_LINK* link = NULL;
// если есть активный burst — используем этот линк напрямую
for (struct ETCP_LINK* l = etcp->links; l; l = l->next) {
if (l->burst_active) { link = l; break; }
}
if (!link) link = etcp_loadbalancer_select_link(etcp);
if (!link) {
etcp->tx_state=ETCP_TX_STATE_LINK_WAIT;
etcp->cnt_link_wait++;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] no link available", etcp->log_name);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no link available", etcp->log_name);
return NULL;// если линков нет - ждём появления свободного
}
etcp->tx_state=ETCP_TX_STATE_DATA_WAIT;
@@ -731,7 +905,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
struct INFLIGHT_PACKET* inf_pkt = (struct INFLIGHT_PACKET*)queue_data_get(etcp->input_send_q);
if (inf_pkt) {
uint64_t now=get_time_tb();
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] send_q->wait_ack seq=%d TS=%llu", etcp->log_name, inf_pkt->seq, now);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] send_q->wait_ack seq=%d TS=%llu", etcp->log_name, inf_pkt->seq, now);
// === NEW: per-link inflight + send_hist logic ===
// Compute 1-based link number in the linked list (as requested)
@@ -753,6 +927,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
inf_pkt->last_link->total_retransmissions++;
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
inf_pkt->last_link->inflight_packets--;
if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
}
// Always add to the CURRENT link (first send or retransmission)
@@ -767,12 +942,12 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
inf_pkt->send_count++;
inf_pkt->state=INFLIGHT_STATE_WAIT_ACK;
queue_data_put_with_index(etcp->input_wait_ack, &inf_pkt->ll, 0, 4);// move dgram to wait_ack queue
queue_data_put_with_index(etcp->input_wait_ack, &inf_pkt->ll);// move dgram to wait_ack queue
}
size_t ack_q_size = queue_entry_count(etcp->ack_q);
if (!inf_pkt && ack_q_size == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] no data/ack to send", etcp->log_name);
if (!inf_pkt && ack_q_size == 0 && !link->burst_active) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no data/ack to send", etcp->log_name);
return NULL;
}
@@ -786,7 +961,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
dgram->noencrypt_len=0;
dgram->timestamp = get_current_timestamp();
// формат ack: [01] [elements count] [4 байта last_delivered_id] и <[4 байта seq][2 байта recv_ts][2 байта txrx delay ts]> x count
// формат ack: [01] [elements count] [4 байта last_delivered_id] [2 байта rx_dup_count] и <[4 байта seq][2 байта recv_ts][2 байта txrx delay ts]> x count
dgram->data[0]=1;// ack
int ptr=2;
@@ -795,9 +970,17 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
dgram->data[ptr++]=etcp->last_delivered_id>>16;
dgram->data[ptr++]=etcp->last_delivered_id>>24;
dgram->data[ptr++]=etcp->rx_dup_count;
dgram->data[ptr++]=etcp->rx_dup_count>>8;
int data_len=0;
if (inf_pkt) data_len=inf_pkt->ll.len;
int remain_len=link->mtu -28/*udp headers*/ -13-8-4/*sc_nonce+tag size+crc*/ -9/*hdr[3] + min ack[6]*/ -5/*payload hdr*/ - data_len;
int remain_len = link->mtu
- 28 /*udp headers*/
- SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE
- (ETCP_ENCRYPTED_HDR_SIZE + ETCP_ACK_BASE_SIZE)
- (1 + sizeof(inf_pkt->seq)) /*payload hdr*/
- data_len;
// DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "remain_len= %d pl=%d", remain_len, data_len);
// добавим опциональные заголовки
@@ -820,14 +1003,49 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
uint16_t dly=get_current_timestamp()-ack_pkt->recv_timestamp;
dgram->data[ptr++]=dly;
dgram->data[ptr++]=dly>>8;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] add ACK N%d dTS=%d", etcp->log_name, ack_pkt->seq, dly);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX Send ACK seq=%d need=%d", etcp->log_name, ack_pkt->seq, etcp->last_delivered_id+1);
queue_entry_free((struct ll_entry*)ack_pkt);
if (inf_pkt && inf_pkt->ll.len+ptr>=etcp->mtu-10) break;// pkt len (надо просчитать точнее включая все заголовки)
if (ptr>500) break;
}
dgram->data[1]=ptr/8;
dgram->data[1]=(ptr - 8) / 8;
int max_enc = link->mtu - 28 - SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE - ETCP_ENCRYPTED_HDR_SIZE;
if (max_enc > PACKET_DATA_SIZE) max_enc = PACKET_DATA_SIZE;
if (max_enc < 0) max_enc = 0;
uint8_t burst_flags = 0; int pkt_sz_pos = 0;
// === MEAS_RESP piggyback (если есть готовый ответ и не активен burst) ===
if (link->burst_resp_pending && !link->burst_active && max_enc - ptr >= MEAS_RESP_SECTION_SIZE) {
dgram->data[ptr++] = ETCP_SECTION_MEAS_RESP;
dgram->data[ptr++] = link->burst_recv_id & 0xFF; dgram->data[ptr++] = (link->burst_recv_id >> 8) & 0xFF;
dgram->data[ptr++] = link->burst_resp_valid;
uint32_t v = link->burst_resp_gap_avg;
dgram->data[ptr++] = v & 0xFF; dgram->data[ptr++] = (v >> 8) & 0xFF; dgram->data[ptr++] = (v >> 16) & 0xFF; dgram->data[ptr++] = (v >> 24) & 0xFF;
v = link->burst_resp_gap_min;
dgram->data[ptr++] = v & 0xFF; dgram->data[ptr++] = (v >> 8) & 0xFF; dgram->data[ptr++] = (v >> 16) & 0xFF; dgram->data[ptr++] = (v >> 24) & 0xFF;
dgram->data[ptr++] = link->burst_resp_pkt_count;
link->burst_resp_pending = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX burst resp id=%u valid=%u gap_min=%u", etcp->log_name, link->burst_recv_id, link->burst_resp_valid, link->burst_resp_gap_min);
}
// === MEAS_TS section (burst) ===
if (link->burst_active && max_enc - ptr >= MEAS_TS_SECTION_SIZE) {
burst_flags = 0;
if (!inf_pkt) burst_flags |= MEAS_FLAG_IS_FILLER;
if (link->burst_seq == 0) burst_flags |= MEAS_FLAG_IS_FIRST;
if (link->burst_seq + 1 >= link->burst_count) burst_flags |= MEAS_FLAG_IS_LAST;
dgram->data[ptr++] = ETCP_SECTION_MEAS_TS;
dgram->data[ptr++] = link->burst_id & 0xFF; dgram->data[ptr++] = (link->burst_id >> 8) & 0xFF;
dgram->data[ptr++] = burst_flags;
dgram->data[ptr++] = link->burst_seq;
uint16_t ts_us = (uint16_t)get_time_us();
dgram->data[ptr++] = ts_us & 0xFF; dgram->data[ptr++] = (ts_us >> 8) & 0xFF;
pkt_sz_pos = ptr;
dgram->data[ptr++] = 0; dgram->data[ptr++] = 0;
}
if (link->last_recv_updated && remain_len>=5) {// если есть данные - добавим channel_timestamp
@@ -851,24 +1069,61 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "remain_len(2)= %d", remain_len);
if (inf_pkt) {
// === FILLER section before PAYLOAD (burst padding) ===
if (link->burst_active) {
int payload_overhead = 1 + 4 + data_len; // type + seq + data
int buf_avail = PACKET_DATA_SIZE - ptr - payload_overhead;
int filler_data = max_enc - ptr - FILLER_HDR_SIZE - payload_overhead;
if (filler_data > buf_avail) filler_data = buf_avail;
if (filler_data > 0) {
dgram->data[ptr++] = ETCP_SECTION_FILLER;
dgram->data[ptr++] = filler_data & 0xFF;
dgram->data[ptr++] = (filler_data >> 8) & 0xFF;
memset(&dgram->data[ptr], 0, filler_data); ptr += filler_data;
}
}
// фрейм data (0) обязательно в конец
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] add DATA (seq=%u, len=%u), ack_size=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, dgram->data[1]);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX DATA: seq=%u len=%u retry=%d; Qlen: in=%d snd=%d wait=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, inf_pkt->send_count, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
dgram->data[ptr++]=0;// payload
dgram->data[ptr++]=inf_pkt->seq;
dgram->data[ptr++]=inf_pkt->seq>>8;
dgram->data[ptr++]=inf_pkt->seq>>16;
dgram->data[ptr++]=inf_pkt->seq>>24;
memcpy(&dgram->data[ptr], inf_pkt->ll.dgram, inf_pkt->ll.len); ptr+=inf_pkt->ll.len;
if ((int)(ptr + inf_pkt->ll.len) <= PACKET_DATA_SIZE) {
memcpy(&dgram->data[ptr], inf_pkt->ll.dgram, inf_pkt->ll.len); ptr+=inf_pkt->ll.len;
} else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload overflow: ptr=%d len=%u max=%d", etcp->log_name, ptr, inf_pkt->ll.len, PACKET_DATA_SIZE);
}
else {
if (link->burst_active) {
int buf_avail = PACKET_DATA_SIZE - ptr;
int filler_data = max_enc - ptr - FILLER_HDR_SIZE;
if (filler_data > buf_avail - FILLER_HDR_SIZE) filler_data = buf_avail - FILLER_HDR_SIZE;
if (filler_data > 0) {
dgram->data[ptr++] = ETCP_SECTION_FILLER;
dgram->data[ptr++] = filler_data & 0xFF;
dgram->data[ptr++] = (filler_data >> 8) & 0xFF;
memset(&dgram->data[ptr], 0, filler_data); ptr += filler_data;
}
}
int chk=queue_check_consistency(etcp->ack_q);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] only ACK (size=%d) packet with %d bytes total (chk=%d) rem=%d", etcp->log_name, ack_q_size, ptr, chk, remain_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] only ACK (size=%d) packet with %d bytes total (chk=%d) rem=%d", etcp->log_name, ack_q_size, ptr, chk, remain_len);
}
if (ptr>=PACKET_DATA_SIZE-50) DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] SIZE ERROR!!! %d", ptr);
if (ptr>=PACKET_DATA_SIZE-50) DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] SIZE ERROR!!! %d", etcp->log_name, ptr);
dgram->data_len=ptr;
// === Fill pkt_sz in MEAS_TS and advance burst state ===
if (link->burst_active && pkt_sz_pos > 0) {
uint16_t pkt_sz = (uint16_t)dgram->data_len;
dgram->data[pkt_sz_pos] = pkt_sz & 0xFF;
dgram->data[pkt_sz_pos + 1] = (pkt_sz >> 8) & 0xFF;
link->burst_pkt_size = pkt_sz;
link->burst_seq++;
if (link->burst_seq >= link->burst_count) etcp_link_burst_finish(link);
}
etcp_dump_pkt_sections(dgram, link, 1);
return dgram;
@@ -889,10 +1144,10 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
// Look for contiguous packets starting from next_expected_id
while (1) {
struct ETCP_FRAGMENT* rx_pkt = (struct ETCP_FRAGMENT*)queue_find_data_by_index(etcp->recv_q, &next_expected_id, 4);
struct ETCP_FRAGMENT* rx_pkt = (struct ETCP_FRAGMENT*)queue_find_data_by_index(etcp->recv_q, &next_expected_id);
if (!rx_pkt) {
// No more contiguous packets found
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] no packet found for id=%u, stopping", etcp->log_name, next_expected_id);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no packet found for id=%u, stopping", etcp->log_name, next_expected_id);
break;
}
@@ -915,7 +1170,7 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
next_expected_id);
// Put it back in recv_q if we can't add to output_queue
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt, 0, 4);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
break;
}
@@ -924,8 +1179,8 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
next_expected_id++;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] delivered %u contiguous packets (%u bytes), last_delivered_id=%u, output_queue_count=%d",
etcp->log_name, delivered_count, delivered_bytes, etcp->last_delivered_id, queue_entry_count(etcp->output_queue));
if (delivered_count>0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] delivered %u contiguous packets (%u bytes), last_delivered_id=%u, asm_queue=%d, output_queue=%d",
etcp->log_name, delivered_count, delivered_bytes, etcp->last_delivered_id, queue_entry_count(etcp->recv_q), queue_entry_count(etcp->output_queue));
}
// Process ACK receipt - remove acknowledged packet from inflight queues
@@ -937,13 +1192,13 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
// Find the acknowledged packet in the wait_ack queue
struct INFLIGHT_PACKET* acked_pkt;
acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_wait_ack, &seq, 4);
acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_wait_ack, &seq);
if (acked_pkt) {
etcp->cnt_ack_hit_inf++;
queue_remove_data(etcp->input_wait_ack, (struct ll_entry*)acked_pkt);
}
else {
acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_send_q, &seq, 4);
acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_send_q, &seq);
if (acked_pkt) {
etcp->cnt_ack_hit_sndq++;
queue_remove_data(etcp->input_send_q, (struct ll_entry*)acked_pkt);
@@ -959,23 +1214,22 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
// === NEW: subtract inflight from the LAST link the packet was sent on ===
if (acked_pkt->last_link) {
// if (acked_pkt->last_link->inflight_bytes >= acked_pkt->ll.len) {
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len;
// } else {
// acked_pkt->last_link->inflight_bytes = 0;
// }
// if (acked_pkt->last_link->inflight_packets > 0) {
acked_pkt->last_link->inflight_packets--;
// }
if (acked_pkt->last_link->send_blocked_inflight && acked_pkt->last_link->inflight_bytes < acked_pkt->last_link->inflight_lim_bytes) loadbalancer_link_ready(acked_pkt->last_link);
}
// Update connection statistics
etcp->unacked_bytes -= acked_pkt->ll.len;
if (etcp->unacked_bytes >= acked_pkt->ll.len) etcp->unacked_bytes -= acked_pkt->ll.len;
else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] unacked_bytes underflow prevented: %u < %u", etcp->log_name, etcp->unacked_bytes, acked_pkt->ll.len);
etcp->unacked_bytes = 0;
}
etcp->bytes_sent_total += acked_pkt->ll.len;
etcp->ack_packets_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] removed packet seq=%u from wait_ack, unacked_bytes now %u total acked=%u", etcp->log_name, seq, etcp->unacked_bytes, etcp->ack_packets_count);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX removed packet seq=%u from wait_ack, unacked_bytes now %u total acked=%u", etcp->log_name, seq, etcp->unacked_bytes, etcp->ack_packets_count);
if (acked_pkt->ll.dgram) {
memory_pool_free(etcp->instance->data_pool, acked_pkt->ll.dgram);
@@ -992,7 +1246,11 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
// Process incoming decrypted packet
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
if (!pkt || !pkt->data_len) return;
if (!pkt) return;
if (!pkt->data_len) {
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
return;
}
etcp_dump_pkt_sections(pkt, pkt->link, 0);
@@ -1001,27 +1259,40 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t len = pkt->data_len;
uint16_t ts = pkt->timestamp; // Received timestamp
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt", etcp->log_name);
while (len >= 1) {
uint8_t type = data[0];
// Process sections as per protocol.txt
switch (type) {
case ETCP_SECTION_ACK: {
if (len < 2) { len = 0; break; }
int elm_cnt=data[1];
uint32_t till=data[2] | (data[3]<<8) | (data[4]<<16) | (data[5]<<24);
int ack_section_len = 6 + elm_cnt * 8;
uint16_t rx_dup_count_16 = data[6] | (data[7]<<8);
uint16_t old_tx_dup_16 = etcp->tx_dup_count & 0xFFFF;
int16_t diff = (int16_t)(rx_dup_count_16 - old_tx_dup_16);
etcp->tx_dup_count += diff;
int ack_section_len = 8 + elm_cnt * 8;
if (ack_section_len > len) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] ACK section too large: elm_cnt=%d len=%d", etcp->log_name, elm_cnt, len);
len = 0;
break;
}
data+=ack_section_len;
len-=ack_section_len;
for (int i=0; i<elm_cnt; i++) {
uint32_t seq=data[-ack_section_len+6+i*8] | (data[-ack_section_len+7+i*8]<<8) | (data[-ack_section_len+8+i*8]<<16) | (data[-ack_section_len+9+i*8]<<24);
uint16_t ts=data[-ack_section_len+10+i*8] | (data[-ack_section_len+11+i*8]<<8);
uint16_t dts=data[-ack_section_len+12+i*8] | (data[-ack_section_len+13+i*8]<<8);
uint32_t seq=data[-ack_section_len+8+i*8] | (data[-ack_section_len+9+i*8]<<8) | (data[-ack_section_len+10+i*8]<<16) | (data[-ack_section_len+11+i*8]<<24);
uint16_t ts=data[-ack_section_len+12+i*8] | (data[-ack_section_len+13+i*8]<<8);
uint16_t dts=data[-ack_section_len+14+i*8] | (data[-ack_section_len+15+i*8]<<8);
etcp_ack_recv(etcp, seq, ts, dts);
}
while ((int32_t)(etcp->rx_ack_till-till)<0) { etcp->rx_ack_till++; etcp_ack_recv(etcp, etcp->rx_ack_till, -1, -1); }// подтверждаем всё по till
break;
}
case ETCP_SECTION_TIMESTAMP: {
if (len < 5) { len = 0; break; }
uint16_t cur_ts=get_current_timestamp();
uint16_t ret_ts=data[1] | (data[2]<<8);// cur_ts=ret_ts = RTT
uint16_t new_rtt=cur_ts-ret_ts;
@@ -1125,17 +1396,24 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
}
}
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
len = 0;
break;
}
p->seq=seq;
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p, 0, 4);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
if (etcp->ack_resp_timer == NULL) {
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
}
if ((int32_t)(etcp->last_delivered_id-seq)<0) if (queue_find_data_by_index(etcp->recv_q, &seq, 4)==NULL) {// проверяем есть ли пакет с этим seq
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
uint32_t pkt_len=len-5;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
// отправляем пакет в очередь на сборку
uint8_t* payload_data = memory_pool_alloc(etcp->instance->data_pool);
if (!payload_data) {
@@ -1156,14 +1434,118 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
rx_pkt->ll.len = pkt_len;
rx_pkt->ll.dgram_pool = etcp->instance->data_pool;
rx_pkt->ll.memlen = etcp->instance->data_pool->object_size;
if (pkt_len > (int)etcp->instance->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload too large for data_pool: %u > %zu", etcp->log_name, pkt_len, etcp->instance->data_pool->object_size);
memory_pool_free(etcp->instance->data_pool, payload_data);
queue_entry_free(&rx_pkt->ll);
len = 0;
break;
}
if (data + len > pkt->data + pkt->data_len) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload section bounds overflow: data+len=%p > end=%p", etcp->log_name, (void*)(data + len), (void*)(pkt->data + pkt->data_len));
memory_pool_free(etcp->instance->data_pool, payload_data);
queue_entry_free(&rx_pkt->ll);
len = 0;
break;
}
// Copy the actual payload data
memcpy(payload_data, data + 5, pkt_len);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt, 0, 4);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] packet seq=%u added to recv_q, calling assembly (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count);
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
} else {
etcp->rx_dup_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX dup: seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count);
}
} else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "payload len %d < 5", len);
len=0;
break;
}
case ETCP_SECTION_MEAS_TS: {
if (len < MEAS_TS_SECTION_SIZE) { len = 0; break; }
uint16_t b_id = data[1] | (data[2] << 8);
uint8_t flags = data[3];
uint8_t seq = data[4];
uint16_t pkt_sz = data[7] | (data[8] << 8);
struct ETCP_LINK* link = pkt->link;
if (b_id != link->burst_recv_id) {
link->burst_recv_id = b_id;
link->burst_recv_count = BURST_PACKET_COUNT;
link->burst_recv_next_seq = 0;
link->burst_recv_valid = 1;
link->burst_recv_received = 0;
}
if (link->burst_recv_valid) {
if (seq != link->burst_recv_next_seq) link->burst_recv_valid = 0;
if (seq < 16) {
link->burst_recv_times[seq] = get_time_us();
link->burst_recv_next_seq = seq + 1;
link->burst_recv_received++;
link->burst_recv_pkt_size = pkt_sz;
}
}
len=0;
if ((flags & MEAS_FLAG_IS_LAST) || link->burst_recv_received >= link->burst_recv_count) {
uint8_t total = (flags & MEAS_FLAG_IS_LAST) ? link->burst_recv_received : link->burst_recv_count;
if (link->burst_recv_valid && total >= BURST_SKIP_COUNT + 2) {
uint32_t sum_gap = 0, min_gap = UINT32_MAX;
uint8_t measured = 0;
for (uint8_t i = BURST_SKIP_COUNT; i + 1 < total; i++) {
uint64_t dt = link->burst_recv_times[i + 1] - link->burst_recv_times[i];
uint32_t gap = (uint32_t)(dt > 0 ? dt : 1);
sum_gap += gap; measured++;
if (gap < min_gap) min_gap = gap;
}
if (measured > 0) {
link->burst_resp_gap_avg = sum_gap / measured;
link->burst_resp_gap_min = min_gap;
link->burst_resp_pkt_count = measured;
link->burst_resp_valid = 1;
}
}
link->burst_resp_pending = 1;
}
data += MEAS_TS_SECTION_SIZE; len -= MEAS_TS_SECTION_SIZE;
break;
}
case ETCP_SECTION_MEAS_RESP: {
if (len < MEAS_RESP_SECTION_SIZE) { len = 0; break; }
uint16_t b_id = data[1] | (data[2] << 8);
uint8_t valid = data[3];
uint32_t gap_avg = data[4] | (data[5] << 8) | ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24);
uint32_t gap_min = data[8] | (data[9] << 8) | ((uint32_t)data[10] << 16) | ((uint32_t)data[11] << 24);
uint8_t pkt_cnt = data[12];
struct ETCP_LINK* link = pkt->link;
if (b_id == link->burst_id - 1 || b_id == link->burst_id) {
if (link->burst_resp_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->burst_resp_timer); link->burst_resp_timer = NULL; }
if (valid == MEAS_RESP_VALID && gap_min > 0 && link->burst_pkt_size > 0) {
uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min;
if (bw_kbps > 10000000) bw_kbps = 10000000;
link->bandwidth = (uint32_t)bw_kbps;
float rtt_sec = (float)link->rtt_min / 10000.f;
if (rtt_sec < 0.005f) rtt_sec = 0.005f;
link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec);
link->slow_start_threshold = link->burst_target_bdp * 9 / 10;
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
if (link->inflight_lim_bytes > link->slow_start_threshold) {
link->inflight_lim_bytes = link->slow_start_threshold;
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
}
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u, slow_start_thr=%u gap_min=%u us pkt_cnt=%u",
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, link->slow_start_threshold, gap_min, pkt_cnt);
}
}
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;
break;
}
case ETCP_SECTION_FILLER: {
if (len < FILLER_HDR_SIZE) { len = 0; break; }
uint16_t fill_len = data[1] | (data[2] << 8);
if ((uint16_t)(fill_len + FILLER_HDR_SIZE) > len) { len = 0; break; }
data += FILLER_HDR_SIZE + fill_len; len -= FILLER_HDR_SIZE + fill_len;
break;
}
@@ -1178,3 +1560,20 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
}
void etcp_update_mtu(struct ETCP_CONN* etcp) {
if (!etcp) return;
int new_mtu = PACKET_DATA_MAX_MTU;
int has_links = 0;
struct ETCP_LINK* link = etcp->links;
while (link) {
has_links = 1;
if (link->mtu > 0 && link->mtu < new_mtu) new_mtu = link->mtu;
link = link->next;
}
if (!has_links) new_mtu = ETCP_RFC791_MIN_MTU;
if (new_mtu != etcp->mtu) {
etcp->mtu = new_mtu;
if (etcp->normalizer) etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
}
}
+32 -3
View File
@@ -28,15 +28,38 @@ uint16_t get_current_timestamp(void);
#define ETCP_SECTION_PAYLOAD 0x00 // Data payload
#define ETCP_SECTION_ACK 0x01 // ACK section
#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed)
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
// Inflight packet states
// Burst measurement constants
#define BURST_PACKET_COUNT 12 // число пакетов в burst
#define BURST_SKIP_COUNT 3 // сколько первых пакетов пропустить при замере gap
#define MIN_BURST_INTERVAL_TB 5000 // минимальный интервал между burst (500ms в 0.1ms)
#define BURST_RESP_TIMEOUT_TB 20000 // таймаут ожидания ответа на burst (2s в 0.1ms)
// Inflight phases
#define INFLIGHT_PHASE_SLOW_START 0
#define INFLIGHT_PHASE_CONG_AVOIDANCE 1
// Section sizes
#define MEAS_TS_SECTION_SIZE 9 // type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2)
#define MEAS_RESP_SECTION_SIZE 13 // type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1)
#define FILLER_HDR_SIZE 3 // type(1) + len(2)
// MEAS_TS flags
#define MEAS_FLAG_IS_FILLER 0x01 // пакет содержит FILLER вместо PAYLOAD
#define MEAS_FLAG_IS_LAST 0x02 // последний пакет в burst
#define MEAS_FLAG_IS_FIRST 0x04 // первый пакет в burst
// MEAS_RESP valid
#define MEAS_RESP_VALID 1
#define INFLIGHT_STATE_WAIT_ACK 0
#define INFLIGHT_STATE_WAIT_SEND 1
#define INFLIGHT_INITIAL_HASH_SIZE 1024
#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак)
#define ASM_BUF_MAX_SIZE (64 * 1024) // максимальный размер буфера сборки фрагментов
// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool.
// пакет полностью удаляется когда приходит ACK (либо conn_reset/close)
@@ -77,6 +100,7 @@ struct ETCP_CONN {
// Links (channels) - linked list
struct ETCP_LINK* links;
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
// Crypto and state
struct secure_channel crypto_ctx;
@@ -149,6 +173,7 @@ struct ETCP_CONN {
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
@@ -164,6 +189,8 @@ struct ETCP_CONN {
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
uint32_t cnt_ack_miss; // счетчик не найденных ack
uint32_t cnt_link_wait; // счетчик переходов в ожидание когда link busy
uint32_t rx_dup_count; // счетчик принятых дубликатов (локальные)
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
uint32_t debug[8]; // 8 значений для дебага (live watch)
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
@@ -208,6 +235,8 @@ void etcp_conn_ready(struct ETCP_CONN* conn);
void etcp_on_link_down(struct ETCP_CONN* etcp);
void etcp_update_mtu(struct ETCP_CONN* etcp);
#ifdef __cplusplus
}
#endif
+19 -26
View File
@@ -57,24 +57,21 @@ int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
return -1;
}
if (id >= ETCP_MAX_BINDINGS) {
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
return -1;
}
if (!callback) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
return -1;
}
if (inst->api_bindings.callbacks[id] != NULL) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: ID %u already bound, overwriting", id);
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
}
inst->api_bindings.callbacks[id] = callback;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound ID %u to callback %p for instance %p",
id, (void*)callback, (void*)inst);
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
return 0;
}
@@ -83,8 +80,8 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
return -1;
}
if (id >= ETCP_MAX_BINDINGS) {
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
return -1;
}
@@ -101,40 +98,36 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
}
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
return -1;
}
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
return -1;
}
if (!conn->normalizer) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
return -1;
}
if (conn->initialized==0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
return -1;
}
struct PKTNORM* pn = conn->normalizer;
if (!pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Normalizer has no input queue");
if (!pn || !pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
return -1;
}
// Помещаем entry в очередь input normalizer
// queue_data_put забирает ownership entry
// DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "Before put to input");
int result = queue_data_put(pn->input, entry);
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "After put to input");
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
if (result != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed (queue full?)");
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: Packet queued for sending to [%s]", conn->log_name);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
return 0;
}
@@ -181,7 +174,7 @@ void etcp_int_recv(struct ll_queue* queue, void* arg) {
}
// Ищем binding для этого ID в instance
if (id < ETCP_MAX_BINDINGS && inst->api_bindings.callbacks[id] != NULL) {
if (inst->api_bindings.callbacks[id] != NULL) {
// Вызываем коллбэк
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
id, (void*)inst->api_bindings.callbacks[id]);
+5
View File
@@ -23,6 +23,11 @@
// ETCP packet IDs
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy через удаленный узел (remote_proxy)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
// Forward declarations
struct ETCP_CONN;
+852 -133
View File
File diff suppressed because it is too large. Load diff
+126 -4
View File
@@ -6,19 +6,26 @@
#include "secure_channel.h"
#include "utun_instance.h"
#include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdint.h>
#include <stddef.h>
#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт]
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define PACKET_DATA_SIZE 1600//1536
#define PACKET_DATA_MAX_MTU 1600
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
#define ETCP_RFC791_MIN_MTU 576
// Типы кодограмм протокола
#define ETCP_INIT_REQUEST 0x02
#define ETCP_INIT_RESPONSE 0x03
#define ETCP_INIT_REQUEST_NOINIT 0x04
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#pragma pack(push, 1)
@@ -32,6 +39,26 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
};
#pragma pack(pop)
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
struct PING_CONTEXT* next;
struct UTUN_INSTANCE* instance;
etcp_ping_callback_t cb;
void* arg;
uint64_t nonce;
void* timeout_timer;
uint64_t send_time; // время отправки пинга в 0.1ms
uint8_t* user_data;
size_t user_data_len;
};
// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений
struct ETCP_SOCKET {
struct ETCP_SOCKET* next; // Linked list для всех соединений
@@ -40,7 +67,6 @@ struct ETCP_SOCKET {
socket_t fd; // UDP socket (cross-platform)
struct sockaddr_storage local_addr; // Локальный адрес
int mtu; // MTU для этого сокета
int loss_rate; // packet loss rate in percent (0-100)
// для входящих подключений (links) - массив упорядоченный по ip_port_hash
size_t max_channels; // сколько выделено памяти
@@ -51,9 +77,43 @@ struct ETCP_SOCKET {
void* socket_id; // Socket ID from uasync_add_socket
uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
uint8_t sock_id; // unique socket id (0-255) for NAT matching
uint8_t nat_type; // NAT_TYPE_* (detected by server)
uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order)
uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers
uint8_t only_local; // 1 = only local connections, no forwarding
struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига
struct sockaddr_storage nat_addr; // NAT адрес (network byte order), ss_family=0 = не определён
};
// NAT check status
#define NAT_CHECK_NONE 0
#define NAT_CHECK_WAITING 1
#define NAT_CHECK_IN_PROGRESS 2
#define NAT_CHECK_EIM 3
#define NAT_CHECK_STRICT 4
// NAT type (detected by server during NAT check)
#define NAT_TYPE_UNKNOWN 0
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
// Verified NAT types (published in nodeinfo after server-side NAT detection)
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive
#define NAT_VERIFIED_EIM 5 // EIM NAT
#define NAT_VERIFIED_STRICT 6 // strict NAT
#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT
// Тип функции-перехватчика отправки UDP.
// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto.
struct ETCP_LINK;
typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* context);
// ETCP Link - одно динамическое соединение (один путь)
struct ETCP_LINK {
uint32_t ip_port_hash; // crc32 для быстрого поиска
@@ -61,7 +121,7 @@ struct ETCP_LINK {
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
SlidingWindowMin* rtt_swm;
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
@@ -75,6 +135,10 @@ struct ETCP_LINK {
uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request)
uint8_t local_link_id; // id моего линка
uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized)
uint8_t remote_socket_id; // socket id peer
uint8_t remote_only_local; // only_local flag from peer
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
@@ -136,6 +200,7 @@ struct ETCP_LINK {
uint16_t rtt_max_val; // Current max RTT in history
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
uint16_t rtt_avg10; // round trip average (excl. max)
uint16_t rtt_min; // round trip min (rtt_swm floating window)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
@@ -145,9 +210,10 @@ struct ETCP_LINK {
// NAT address tracking (from INIT_RESPONSE)
uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set
uint16_t nat_port; // NAT port (network byte order)
uint16_t nat_port; // NAT port (host byte order)
uint32_t nat_changes_count; // Counter of NAT address changes
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
uint8_t nat_check_status; // NAT_CHECK_*
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
@@ -157,8 +223,46 @@ struct ETCP_LINK {
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
uint16_t handshake_minsize; // минимальный размер udp при handshake
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
// === Burst-измерение bandwidth ===
// Sender-сторона
uint16_t burst_id; // монотонно возрастающий ID burst
uint8_t burst_active; // 1 = burst в процессе отправки
uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1)
uint8_t burst_count; // общее число пакетов в burst
uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL
void* burst_resp_timer; // таймер ожидания ответа на burst
uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты)
uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW
// Receiver-сторона
uint16_t burst_recv_id; // ID отслеживаемого burst
uint8_t burst_recv_count; // ожидаемое число пакетов в burst
uint8_t burst_recv_next_seq; // следующий ожидаемый seq
uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен
uint8_t burst_recv_received; // сколько пакетов уже принято
uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs)
uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа)
uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback
uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs)
uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs)
uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения
// === Фазы управления inflight ===
// 0 = slow_start, 1 = congestion_avoidance
uint8_t inflight_phase;
uint64_t last_window_update_tb; // время последнего обновления окна (0.1ms)
uint32_t slow_start_threshold; // порог выхода из slow start (burst_target_bdp * 0.9)
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx;
};
// Единая точка отправки всех ETCP-пакетов.
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len);
// INITIALIZATION
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
int init_sockets(struct UTUN_INSTANCE* instance);
@@ -189,4 +293,22 @@ void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
void start_stats_timer(struct ETCP_LINK* link);
// Burst measurement functions
void etcp_link_burst_start(struct ETCP_LINK* link);
void etcp_link_burst_check(struct ETCP_LINK* link);
void etcp_link_burst_finish(struct ETCP_LINK* link);
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback.
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr,
int timeout_ms, etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
#endif // ETCP_CONNECTIONS_H
+4 -3
View File
@@ -42,11 +42,12 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
case ETCP_SECTION_ACK: {
if (offset + 2 > len) { offset = len; break; }
ack_count = data[offset + 1];
if (offset + 6 > len) { offset = len; break; }
if (offset + 8 > len) { offset = len; break; }
ack_last_delivered = data[offset + 2] | (data[offset + 3] << 8) |
(data[offset + 4] << 16) | (data[offset + 5] << 24);
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d", ack_last_delivered, ack_count);
offset += 6 + ack_count * 8;
uint16_t ack_rx_dup = data[offset + 6] | (data[offset + 7] << 8);
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d dup=%u", ack_last_delivered, ack_count, ack_rx_dup);
offset += 8 + ack_count * 8;
break;
}
case ETCP_SECTION_TIMESTAMP: {
+255
View File
@@ -0,0 +1,255 @@
#include "etcp_dump.h"
#include "etcp.h"
#include "pkt_normalizer.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdio.h>
#include <string.h>
#define DUMP_FP stdout
#define DUMP_LINE(fmt, ...) do { \
fprintf(DUMP_FP, " " fmt "\n", ##__VA_ARGS__); \
} while(0)
#define DUMP_HDR(fmt, ...) do { \
fprintf(DUMP_FP, fmt "\n", ##__VA_ARGS__); \
} while(0)
static void dump_queue(const char* name, struct ll_queue* q) {
if (!q) { DUMP_LINE("%-14s NULL", name); return; }
DUMP_LINE("%-14s %d pkts / %zu bytes", name, queue_entry_count(q), queue_total_bytes(q));
}
static void dump_pool(const char* name, struct memory_pool* pool) {
if (!pool) return;
size_t allocs, reuse;
memory_pool_get_stats(pool, &allocs, &reuse);
DUMP_LINE(" %-12s alloc=%zu reuse=%zu", name, allocs, reuse);
}
static void dump_link_timer(const char* name, void* timer) {
DUMP_LINE(" %-14s %s", name, timer ? "ACTIVE" : "free");
}
static const char* ip_to_str_buf(uint32_t ip, char* buf, size_t sz) {
uint8_t* b = (uint8_t*)&ip;
snprintf(buf, sz, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
return buf;
}
static const char* sockaddr_to_str(const struct sockaddr_storage* addr, char* buf, size_t sz) {
if (!addr || addr->ss_family != AF_INET) { snprintf(buf, sz, "?"); return buf; }
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
uint8_t* b = (uint8_t*)&sin->sin_addr.s_addr;
snprintf(buf, sz, "%u.%u.%u.%u:%u", b[0], b[1], b[2], b[3], ntohs(sin->sin_port));
return buf;
}
void etcp_dump_conn_state(struct ETCP_CONN* conn) {
if (!conn) { DUMP_HDR("etcp_dump_conn_state: NULL conn"); return; }
char addr_buf[32], nat_buf[32];
DUMP_HDR("=== ETCP CONN STATE [%s] ===", conn->log_name);
/* --- GENERAL --- */
DUMP_LINE("GENERAL: %s peer=0x%llx initialized=%d links_up=%d tx_state=%d session=0x%08x mtu=%d routing_ex=%d",
conn->name ? conn->name : "",
(unsigned long long)conn->peer_node_id, conn->initialized, conn->links_up,
conn->tx_state, conn->session_id, conn->mtu, conn->routing_exchange_active);
/* --- IDS --- */
DUMP_LINE("IDS: next_tx=%u last_rx=%u last_del=%u rx_ack_till=%u",
conn->next_tx_id, conn->last_rx_id, conn->last_delivered_id, conn->rx_ack_till);
/* --- RTT --- */
DUMP_LINE("RTT: last=%u avg10=%u avg100=%u jitter=%u",
conn->rtt_last, conn->rtt_avg_10, conn->rtt_avg_100, conn->jitter);
/* --- STATS --- */
DUMP_LINE("STATS: bytes_sent=%u retrans=%u reinit=%u reset=%u ack_pkts=%u",
conn->bytes_sent_total, conn->retransmissions_count,
conn->reinit_count, conn->reset_count, conn->ack_packets_count);
/* --- INFLIGHT --- */
DUMP_LINE("INFLIGHT: unacked=%u optimal=%u",
conn->unacked_bytes, conn->optimal_inflight);
/* --- ACK DEBUG --- */
DUMP_LINE("ACK_DEBUG: hit_inf=%u hit_sndq=%u miss=%u link_wait=%u rx_dup=%u tx_dup=%u",
conn->cnt_ack_hit_inf, conn->cnt_ack_hit_sndq, conn->cnt_ack_miss,
conn->cnt_link_wait, conn->rx_dup_count, conn->tx_dup_count);
/* --- DEBUG[8] --- */
DUMP_LINE("DEBUG: [0]=%u [1]=%u [2]=%u [3]=%u [4]=%u [5]=%u [6]=%u [7]=%u",
conn->debug[0], conn->debug[1], conn->debug[2], conn->debug[3],
conn->debug[4], conn->debug[5], conn->debug[6], conn->debug[7]);
/* --- TIMERS --- */
DUMP_LINE("TIMERS: retrans=%s ack_resp=%s",
conn->retrans_timer ? "ACTIVE" : "free",
conn->ack_resp_timer ? "ACTIVE" : "free");
/* --- QUEUES --- */
DUMP_HDR(" --- QUEUES ---");
dump_queue("input", conn->input_queue);
dump_queue("input_send_q", conn->input_send_q);
dump_queue("input_wait_ack", conn->input_wait_ack);
dump_queue("ack_q", conn->ack_q);
dump_queue("recv_q", conn->recv_q);
dump_queue("output", conn->output_queue);
/* --- NORMALIZER --- */
if (conn->normalizer) {
struct PKTNORM* pn = (struct PKTNORM*)conn->normalizer;
DUMP_HDR(" --- NORMALIZER ---");
DUMP_LINE("frag=%u data_ptr=%u/%u flush=%s pending=%s recvpart=%s",
pn->frag_size, pn->data_ptr, pn->data_size,
pn->flush_timer ? "ACTIVE" : "free",
pn->pending ? "ACTIVE" : "free",
pn->recvpart ? "ACTIVE" : "free");
dump_queue("pn-input", pn->input);
dump_queue("pn-output", pn->output);
DUMP_LINE("alloc_err=%u logic_err=%u in=%llu/%llu out=%llu/%llu",
pn->alloc_errors, pn->logic_errors,
(unsigned long long)pn->in_total_pkts, (unsigned long long)pn->in_total_bytes,
(unsigned long long)pn->out_total_pkts, (unsigned long long)pn->out_total_bytes);
} else {
DUMP_HDR(" --- NORMALIZER --- NULL");
}
/* --- POOLS --- */
DUMP_HDR(" --- POOLS ---");
dump_pool("inflight", conn->inflight_pool);
dump_pool("io", conn->io_pool);
if (conn->instance) {
dump_pool("data", conn->instance->data_pool);
dump_pool("ack", conn->instance->ack_pool);
dump_pool("pkt", conn->instance->pkt_pool);
}
/* --- LINKS --- */
int link_idx = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
DUMP_HDR(" --- LINK %d ---", link_idx);
sockaddr_to_str(&link->remote_addr, addr_buf, sizeof(addr_buf));
/* basic link state */
DUMP_LINE("BASIC: id=%d/%d is_server=%d link_state=%d link_status=%d initialized=%d mtu=%d",
link->local_link_id, link->remote_link_id, link->is_server,
link->link_state, link->link_status, link->initialized, link->mtu);
DUMP_LINE("ADDR: remote=%s remote_sock=%d remote_type=%d remote_only_local=%d",
addr_buf, link->remote_socket_id, link->remote_type, link->remote_only_local);
/* keepalive */
DUMP_LINE("KA: recv=%d remote=%d sent=%u recv=%u interval=%u timeout=%u",
link->recv_keepalive, link->remote_keepalive,
link->keepalive_sent_count, link->keepalive_recv_count,
link->keepalive_interval, link->keepalive_timeout);
/* timers */
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s",
link->init_timer ? "ACTIVE" : "free",
link->init_timeout, link->init_retry_count,
link->keepalive_timer ? "ACTIVE" : "free",
link->shaper_timer ? "ACTIVE" : "free",
link->stats_timer ? "ACTIVE" : "free",
link->burst_resp_timer ? "ACTIVE" : "free",
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* inflight */
DUMP_LINE("INFLIGHT: bytes=%u pkts=%u lim=%u blocked=%d phase=%d sst=%u last_win_tb=%llu",
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
link->send_blocked_inflight, link->inflight_phase,
link->slow_start_threshold, (unsigned long long)link->last_window_update_tb);
/* rtt */
char hist_str[128] = "";
int hist_pos = 0;
for (int i = 0; i < 10 && i < (int)link->rtt_history_count; i++) {
hist_pos += snprintf(hist_str + hist_pos, sizeof(hist_str) - hist_pos,
"%s%u", i == 0 ? "" : ",", link->rtt_history[i]);
}
DUMP_LINE("RTT: last=%u avg10=%u min=%u max=%u jitter=%u hist=[%s] cnt=%d swm=%u",
link->rtt_last, link->rtt_avg10, link->rtt_min, link->rtt_max_val,
link->jitter, hist_str, link->rtt_history_count,
link->rtt_swm ? swm_get_min(link->rtt_swm) : 0);
/* tt/rt/bandwidth */
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
link->tt_last, link->rt_last,
link->recv_dt_avg_tx, link->recv_dt_avg_rx,
link->bandwidth);
/* errors */
DUMP_LINE("MTU: %u local=%u remote=%u", link->mtu, link->mtu_local, link->mtu_remote);
DUMP_LINE("ERRORS: enc=%zu dec=%zu snd=%zu rcv=%zu total_enc=%zu total_dec=%zu retrans=%u",
link->encrypt_errors, link->decrypt_errors,
link->send_errors, link->recv_errors,
link->total_encrypted, link->total_decrypted,
link->total_retransmissions);
/* nat */
ip_to_str_buf(link->nat_ip, nat_buf, sizeof(nat_buf));
DUMP_LINE("NAT: ip=%s:%u chg=%u hits=%u check=%d type=%d",
nat_buf, link->nat_port,
link->nat_changes_count, link->nat_hits_count,
link->nat_check_status, link->nat_type);
/* stats win */
DUMP_LINE("WIN: ptr=%u tb=%u tx=%u retrans=%u",
link->win_ptr, link->win_timebase,
link->window_pkt_transmitted, link->window_retransmissions);
/* last recv */
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
(unsigned long long)link->last_recv_local_time,
link->last_recv_timestamp, link->last_recv_updated);
/* handshake */
DUMP_LINE("HANDSHAKE: min=%u max=%u", link->handshake_minsize, link->handshake_maxsize);
/* burst */
DUMP_LINE("BURST: active=%d id=%u seq=%u/%u last_tb=%llu resp=%s",
link->burst_active, link->burst_id,
link->burst_seq, link->burst_count,
(unsigned long long)link->burst_last_time_tb,
link->burst_resp_timer ? "ACTIVE" : "free");
/* shaper */
DUMP_LINE("SHAPER: load_tb=%llu sub=%llu state=%d",
(unsigned long long)link->shaper_load_time_tb,
(unsigned long long)link->shaper_sub_nanotime,
link->shaper_state);
link = link->next;
link_idx++;
}
if (link_idx == 0) DUMP_HDR(" --- NO LINKS ---");
DUMP_HDR("=== END [%s] ===", conn->log_name);
}
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance) {
if (!instance) {
DUMP_HDR("etcp_dump_all_conns: NULL instance");
return;
}
DUMP_HDR("=== DUMP ALL CONNS for instance node=0x%llx ===",
(unsigned long long)instance->node_id);
struct ETCP_CONN* conn = instance->connections;
int idx = 0;
while (conn) {
DUMP_HDR("--- CONN %d ---", idx);
etcp_dump_conn_state(conn);
conn = conn->next;
idx++;
}
if (idx == 0) DUMP_HDR("--- NO CONNECTIONS ---");
DUMP_HDR("=== END DUMP ALL ===");
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef ETCP_DUMP_H
#define ETCP_DUMP_H
#include "etcp.h"
#include "utun_instance.h"
void etcp_dump_conn_state(struct ETCP_CONN* conn);
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance);
#endif
+71 -40
View File
@@ -25,6 +25,7 @@
static void shaper_timer_cb(void* arg); // Shaper wait callback
// Select link for transmission
// Algorithm: min inflight_bytes, round-robin among ties
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
if (!etcp || !etcp->links) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] invalid parameters (etcp=%p, links=%p)",
@@ -32,19 +33,12 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
return NULL;
}
struct ETCP_LINK* best = NULL;
uint64_t min_load_tb = UINT64_MAX;
uint64_t now_tb=get_time_tb();
// First pass: find minimum inflight_bytes among available links
uint32_t min_inflight = UINT32_MAX;
int available_count = 0;
struct ETCP_LINK* link = etcp->links;
int link_index = 0;
while (link) {
link_index++;
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu",
// link_index, link, link->initialized, link->shaper_timer==NULL?1:0,
// (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime);
if (!link->initialized || link->link_status != 1) {
link = link->next;
continue;
@@ -54,40 +48,73 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
continue;
}
if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA;
// Check if ready (load < now + burst allowance)
uint64_t effective_load_ns = link->shaper_load_time_tb * TIMEBASE_NS + (link->shaper_sub_nanotime / 10); // To ns
uint64_t now_ns = now_tb * TIMEBASE_NS;
int64_t delta_t=(int64_t)(now_ns - effective_load_ns);// uint64 может переполняться. чтобы обеспечить правильную цикличность.
if (link->shaper_load_time_tb < min_load_tb || (link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) {
min_load_tb = link->shaper_load_time_tb;
best = link;
available_count++;
if (link->inflight_bytes < min_inflight) {
min_inflight = link->inflight_bytes;
}
link = link->next;
}
if (best) {
// DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] selected link %p (load_tb=%llu)",
// etcp->log_name, best, (unsigned long long)min_load_tb);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found", etcp->log_name);
if (available_count == 0) {
if (etcp->links)
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: inf:%s, tmr:%s",
etcp->log_name, etcp->links->send_blocked_inflight?"wait":"rdy", etcp->links->shaper_timer?"wait":"rdy");
return NULL;
}
return best;
// Count how many links have the minimum inflight
int min_count = 0;
link = etcp->links;
while (link) {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
min_count++;
}
link = link->next;
}
if (min_count == 1) {
// Only one link with minimum inflight - return it directly
link = etcp->links;
while (link) {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
return link;
}
link = link->next;
}
}
// Round-robin among links with minimum inflight
// Start from the link after last_rr_link (or from the beginning if last_rr_link is NULL)
struct ETCP_LINK* start = etcp->last_rr_link ? etcp->last_rr_link->next : etcp->links;
if (!start) start = etcp->links;
link = start;
do {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
etcp->last_rr_link = link;
return link;
}
link = link->next;
if (!link) link = etcp->links;
} while (link != start);
// Should not reach here, but return NULL just in case
return NULL;
}
// New: Send dgram (select link, encrypt/send, update shaper)
void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
if (!dgram) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: called with NULL dgram");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "called with NULL dgram");
return;
}
struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL;
if (!etcp) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] no ETCP_CONN associated with dgram", etcp->log_name);
memory_pool_free(etcp->instance->pkt_pool, dgram);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no ETCP_CONN associated with dgram (link=%p)", (void*)dgram->link);
return;
}
@@ -114,12 +141,14 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
}
// Update shaper after successful send
if (link->bandwidth == 0) {
if (link->burst_active) {
// burst bypasses shaper — не обновляем нагрузку
} else if (link->bandwidth == 0) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] unlimited bandwidth, skipping shaper update", etcp->log_name);
// Don't return here, let the function continue to free dgram at the end
} else {
// Time to transmit (ns per byte = 8e9 / bw for bits/sec)
double byte_time_ns = 8000000000.0 / (double)link->bandwidth;
// Time to transmit (ns per byte = 8e6 / bw for Kbits/sec)
double byte_time_ns = 8000000.0 / (double)link->bandwidth;
uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns + 0.5); // Round nearest
// To sub_nanotime (*10 for 0.1ns)
@@ -135,8 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
uint64_t now_tb=get_time_tb();
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu)", etcp->log_name, (unsigned long long)wait_tb);
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb, "shaper");
if (link->shaper_timer == NULL) DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Filed to allocate timer");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
}
// Inactivity correction
@@ -150,6 +180,7 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
if (!link) return 0;
if (link->burst_active) return 1; // burst bypasses all limits
int can = 1;
if (link->inflight_bytes >= link->inflight_lim_bytes) {
link->send_blocked_inflight = 1;
@@ -168,25 +199,25 @@ int loadbalancer_link_can_send(struct ETCP_LINK* link) {
void loadbalancer_link_ready(struct ETCP_LINK* link) {
if (!link || !link->etcp) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: invalid link (%p)", link);
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
return;
}
if (loadbalancer_link_can_send(link) == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link still blocked");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link=%p now ready, notifying ETCP_CONN", link);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
if (link->etcp->link_ready_for_send_fn) {
link->etcp->link_ready_for_send_fn(link->etcp);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: no link_ready_for_send_fn set");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "no link_ready_for_send_fn set");
}
}
// Get ETCP link status: 1 = at least one link is up, 0 = all links down or no links
int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
if (!etcp) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_get_link_status: NULL etcp");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "NULL etcp");
return 0;
}
@@ -201,7 +232,7 @@ int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] link status check: %d alive links",
etcp->log_name ? etcp->log_name : "????→????", alive_count);
etcp->log_name[0] ? etcp->log_name : "????→????", alive_count);
return (alive_count > 0) ? 1 : 0;
}
@@ -212,7 +243,7 @@ static void shaper_timer_cb(void* arg) {
if (!link) return;
link->shaper_timer = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "shaper_timer_cb: link=%p now ready", link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link=%p now ready", link);
// Notify loadbalancer that link is ready
loadbalancer_link_ready(link);
+597
View File
@@ -0,0 +1,597 @@
// etcp_router.c — Сервисный слой маршрутизации поверх ETCP
// Упрощённый TCP: восстановление порядка (recv_q), дедупликация, без переповторов
// ACK: периодическая отправка rx_seq (не чаще 100ms), idle-таймер для последнего seq
// Inflight контроль через send_q: при переполнении — очередь + retry-таймер, без дропов
#include "etcp_router.h"
#include "etcp.h"
#include "utun_instance.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
#include <string.h>
// ====================================================================
// Внутренние forward declarations
// ====================================================================
static void router_ack_timer_cb(void* arg);
static void router_idle_ack_timer_cb(void* arg);
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags);
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
static void router_send_resume_cb(void* arg);
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn);
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn);
// ====================================================================
// Управление ROUTER_CONN
// ====================================================================
static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id) {
if (!inst || !inst->router_conns) return NULL;
uint8_t key[9];
memcpy(key, &remote_node_id, 8);
key[8] = svc_id;
return (struct ETCP_ROUTER_CONN*)queue_find_data_by_index(inst->router_conns, key);
}
// ====================================================================
// Отправка: router_send_one + send_q
// ====================================================================
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
return router_send_one_flags(rconn, payload, pl_len, 0);
}
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
struct UTUN_INSTANCE* inst = rconn->inst;
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
? 0 : rconn->tx_seq++;
seq |= seq_flags;
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len;
uint8_t* dgram = u_malloc(total_len);
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = seq;
hdr->svc_id = rconn->svc_id;
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, payload, pl_len);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
entry->dgram = dgram;
entry->len = total_len;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_send_one: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(entry); queue_dgram_free(entry); if (!seq_flags) rconn->tx_seq--; return -1;
}
rconn->last_dgram_ts = get_current_timestamp();
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "router_send: seq=%u → %016llx svc_id=%u len=%zu inflight=%u",
seq, (unsigned long long)rconn->remote_node_id, rconn->svc_id, pl_len,
rconn->tx_seq - rconn->rx_acked);
return etcp_send(conn, entry);
}
// Отправка без conn (RST для неизвестного src)
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags) {
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, dst);
if (!conn) return;
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) return;
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = dst;
hdr->src_node_id = inst->node_id;
hdr->seq = seq_flags;
hdr->svc_id = svc_id;
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
entry->len = SVC_ROUTE_HDR_SIZE;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
seq_flags, (unsigned long long)dst, svc_id);
etcp_send(conn, entry);
}
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
struct UTUN_INSTANCE* inst = rconn->inst;
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
if (!cb) return;
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(1);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = rconn->svc_id;
e->len = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_close_svc: svc_id=%u remote=%016llx",
rconn->svc_id, (unsigned long long)rconn->remote_node_id);
cb(NULL, e);
}
// Закрыть conn: отправить CLOSE удалённой стороне, уведомить локальный сервис, очистить
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
if (!rconn) return;
struct UTUN_INSTANCE* inst = rconn->inst;
router_send_close_to_service(rconn);
// Отправляем CLOSE удалённой стороне
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG);
// Таймеры
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q); rconn->recv_q = NULL;
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q); rconn->send_q = NULL;
}
if (inst->router_conns)
queue_remove_data(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: closed remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
}
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
while (1) {
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) break;
struct ll_entry* e = queue_data_get(rconn->send_q);
if (!e) { rconn->send_blocked = 0; break; }
router_send_one(rconn, e->dgram, e->len);
queue_dgram_free(e); queue_entry_free(e);
}
if (rconn->send_blocked && !rconn->send_resume_timer)
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
if (!rconn->send_blocked && rconn->send_resume_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->send_resume_timer);
rconn->send_resume_timer = NULL;
}
}
static void router_send_resume_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->send_resume_timer = NULL;
router_drain_send_q(rconn);
}
static void router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
struct ll_entry* qe = queue_entry_new(0);
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_enqueue_send: queue_entry_new failed"); return; }
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_entry_free(qe); return; }
qe->len = pl_len;
if (pl_len > 0) memcpy(qe->dgram, payload, pl_len);
queue_data_put(rconn->send_q, qe);
rconn->send_blocked = 1;
if (!rconn->send_resume_timer)
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_send_q: queued svc_id=%u inflight=%u send_q=%d",
rconn->svc_id, rconn->tx_seq - rconn->rx_acked, queue_entry_count(rconn->send_q));
}
// ====================================================================
// ACK timers
// ====================================================================
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
if (rconn->idle_ack_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->idle_ack_timer);
rconn->idle_ack_timer = NULL;
}
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
}
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
struct UTUN_INSTANCE* inst = rconn->inst;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_ack: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
return;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_ack: u_malloc failed"); return; }
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = rconn->rx_seq;
hdr->svc_id = rconn->svc_id;
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
entry->len = SVC_ROUTE_HDR_SIZE;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_ack: → %016llx svc_id=%u rx_seq=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id, rconn->rx_seq);
etcp_send(conn, entry);
}
static void router_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->ack_timer = NULL;
if (rconn->rx_seq != rconn->rx_acked) {
router_send_ack(rconn);
rconn->rx_acked = rconn->rx_seq;
}
if (rconn->rx_seq != rconn->rx_acked)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
else
rconn->idle_ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_IDLE_TB, rconn, router_idle_ack_timer_cb, "router_idle_ack");
}
static void router_idle_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->idle_ack_timer = NULL;
if (rconn->rx_seq != rconn->rx_acked) {
router_send_ack(rconn);
rconn->rx_acked = rconn->rx_seq;
}
}
// ====================================================================
// Reorder / assembly (аналог etcp_output_try_assembly)
// ====================================================================
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn,
const uint8_t* payload, size_t payload_len) {
struct UTUN_INSTANCE* inst = rconn->inst;
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
if (!cb) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_deliver: no handler for svc_id=%u", rconn->svc_id);
return;
}
size_t entry_len = 1 + payload_len;
struct ll_entry* svc_entry = queue_entry_new(0);
if (!svc_entry) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_deliver: queue_entry_new failed"); return; }
svc_entry->len = entry_len;
svc_entry->dgram = u_malloc(entry_len);
if (!svc_entry->dgram) { queue_entry_free(svc_entry); return; }
svc_entry->dgram[0] = rconn->svc_id;
if (payload_len > 0) memcpy(svc_entry->dgram + 1, payload, payload_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
cb(conn, svc_entry);
}
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn) {
uint32_t next = rconn->rx_seq;
while (1) {
struct ll_entry* entry = queue_find_data_by_index(rconn->recv_q, &next);
if (!entry) break;
queue_remove_data(rconn->recv_q, entry);
router_deliver(rconn, conn, entry->dgram, entry->len);
rconn->rx_seq = next + 1;
next++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
}
// ====================================================================
// etcp_router_recv_cb — обработчик ETCP_ID_SVC_ROUTE
// ====================================================================
static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry) return;
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
if (!inst || entry->len < SVC_ROUTE_HDR_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router: invalid packet inst=%p len=%zu min=%u",
(void*)inst, entry->len, (unsigned)SVC_ROUTE_HDR_SIZE);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)entry->dgram;
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
if (hdr->dst_node_id == inst->node_id) {
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
// CLOSE или RST — pl_len==0 и флаг в seq (закрываем conn)
if (pl_len == 0 && (hdr->seq & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
if (rconn) router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (pl_len == 0) {
// ACK-пакет: обновляем rx_acked, drain send_q, выходим
if (rconn) {
rconn->rx_acked = hdr->seq;
rconn->last_dgram_ts = get_current_timestamp();
if (rconn->send_blocked) router_drain_send_q(rconn);
}
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Data-пакет — авто-создаём conn если ещё нет
if (!rconn) {
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
}
// Seq-состояние есть — reorder
rconn->last_dgram_ts = get_current_timestamp();
uint32_t seq = hdr->seq;
// Проверка границ (32-bit circular, аналог ETCP MAX_INFLIGHT_SIZE)
{
int32_t d = (int32_t)(seq - rconn->rx_seq);
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
seq, rconn->rx_seq, d);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
// Дубликат (seq уже доставлен или в recv_q)
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
queue_find_data_by_index(rconn->recv_q, &seq)) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Кладём в recv_q
struct ll_entry* qe = queue_entry_new(4);
if (!qe) { queue_dgram_free(entry); queue_entry_free(entry); return; }
*(uint32_t*)qe->data = seq;
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
qe->len = pl_len;
memcpy(qe->dgram, pl, pl_len);
queue_data_put_with_index(rconn->recv_q, qe);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
(unsigned long long)hdr->src_node_id, rconn->svc_id);
if (seq == rconn->rx_seq)
router_try_assembly(rconn, conn);
router_schedule_ack(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
} else {
// ========== Транзит ==========
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
if (!next) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_router: forwarding svc_id=%u → %016llx via %s",
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
etcp_send(next, entry);
}
}
// ====================================================================
// Public API
// ====================================================================
int etcp_router_init(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: NULL instance"); return -1; }
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
inst->router_conns = queue_new(inst->ua,
ROUTER_CONN_HASH_SIZE, 0, 9, "router_conns");
if (!inst->router_conns) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: queue_new(router_conns) failed");
return -1;
}
int ret = etcp_bind(inst, ETCP_ID_SVC_ROUTE, etcp_router_recv_cb);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: etcp_bind failed, ret=%d", ret);
queue_free(inst->router_conns); inst->router_conns = NULL;
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
return 0;
}
void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
etcp_unbind(inst, ETCP_ID_SVC_ROUTE);
if (inst->router_conns) {
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
router_send_close_to_service(rconn);
if (rconn->ack_timer) uasync_cancel_timeout(inst->ua, rconn->ack_timer);
if (rconn->idle_ack_timer) uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer);
if (rconn->send_resume_timer) uasync_cancel_timeout(inst->ua, rconn->send_resume_timer);
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
queue_entry_free(entry);
}
queue_free(inst->router_conns);
inst->router_conns = NULL;
}
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router destroyed");
}
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_bind: NULL instance"); return -1; }
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_bind: NULL callback for svc_id=%u", svc_id); return -1; }
if (inst->router_bindings.callbacks[svc_id])
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router_bind: overwriting svc_id=%u", svc_id);
inst->router_bindings.callbacks[svc_id] = callback;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router_bind: svc_id=%u → cb=%p", svc_id, (void*)callback);
return 0;
}
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: NULL instance"); return -1; }
if (!inst->router_bindings.callbacks[svc_id]) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: svc_id=%u not bound", svc_id);
return -1;
}
inst->router_bindings.callbacks[svc_id] = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: svc_id=%u", svc_id);
return 0;
}
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry) {
if (!inst || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_route_send: NULL inst=%p entry=%p", (void*)inst, (void*)entry);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1;
}
if (!entry->dgram || entry->len < 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_route_send: empty entry"); queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
uint8_t svc_id = entry->dgram[0];
size_t payload_len = entry->len - 1;
if (dst_node_id == inst->node_id) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_route_send: loopback svc_id=%u len=%zu", svc_id, payload_len);
if (inst->router_bindings.callbacks[svc_id])
inst->router_bindings.callbacks[svc_id](NULL, entry);
else { queue_dgram_free(entry); queue_entry_free(entry); }
return 0;
}
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
if (!etcp_conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
(unsigned long long)dst_node_id, svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return -1;
}
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, dst_node_id, svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) {
router_enqueue_send(rconn, entry->dgram + 1, payload_len);
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
int ret = router_send_one(rconn, entry->dgram + 1, payload_len);
queue_dgram_free(entry); queue_entry_free(entry);
return ret;
}
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
if (!inst || !inst->bgp) return -1;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return -1;
return queue_entry_count(conn->normalizer->input);
}
// ====================================================================
// Seq-connection API
// ====================================================================
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id) {
if (!inst || !inst->router_conns) return NULL;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
if (rconn) return rconn;
size_t data_size = sizeof(struct ETCP_ROUTER_CONN) - sizeof(struct ll_entry);
rconn = (struct ETCP_ROUTER_CONN*)queue_entry_new(data_size);
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_entry_new failed"); return NULL; }
rconn->remote_node_id = remote_node_id;
rconn->svc_id = svc_id;
rconn->last_dgram_ts = get_current_timestamp();
rconn->tx_seq = 0;
rconn->rx_seq = 0;
rconn->rx_acked = 0;
rconn->inst = inst;
rconn->ack_timer = NULL;
rconn->idle_ack_timer = NULL;
rconn->send_blocked = 0;
rconn->send_resume_timer = NULL;
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_data_put_with_index(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: new conn remote=%016llx svc_id=%u",
(unsigned long long)remote_node_id, svc_id);
return rconn;
}
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len) {
if (!rconn || !data || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_send: invalid args rconn=%p data=%p len=%zu",
(void*)rconn, (const void*)data, len);
return -1;
}
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) {
router_enqueue_send(rconn, data, len);
return 0;
}
return router_send_one(rconn, data, len);
}
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
router_close_and_notify(rconn);
}
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id) {
if (!inst || !inst->router_conns) return;
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
if (rconn->remote_node_id == remote_node_id) {
router_send_close_to_service(rconn);
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: closed by node remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
} else {
queue_data_put(inst->router_conns, entry);
}
}
}
+94
View File
@@ -0,0 +1,94 @@
// etcp_router.h — Сервисный слой маршрутизации поверх ETCP
// Маршрутизирует сервисные пакеты до целевой ноды, на промежуточных нодах ретранслирует
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [payload...]
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
#include <stdint.h>
#include "etcp_api.h"
#pragma pack(push, 1)
struct SVC_ROUTE_HDR {
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
uint64_t dst_node_id;
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
uint8_t svc_id; // идентификатор сервиса
};
#pragma pack(pop)
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 22
#define SVC_ROUTE_MAX_BINDINGS 256
// Состояние одного логического подключения (remote_node_id + svc_id)
struct ETCP_ROUTER_CONN {
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
uint64_t remote_node_id; // = data[0..7]
uint8_t svc_id; // = data[8]
uint16_t last_dgram_ts; // последний timestamp датаграммы (rx или tx, timebase 0.1ms)
uint32_t tx_seq; // следующий seq для отправки
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
uint32_t rx_acked; // последний отправленный ACK (= rx_seq на момент отправки)
struct UTUN_INSTANCE* inst;
struct ll_queue* recv_q; // reorder очередь: hash по seq (4 байта, offset 0)
void* ack_timer; // периодический таймер (100ms)
void* idle_ack_timer; // idle таймер (дослать последний ack)
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
void* send_resume_timer; // таймер возобновления отправки
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
};
#define ROUTER_CONN_HASH_SIZE 256
#define ROUTER_RECVQ_HASH_SIZE 1024
#define ROUTER_MAX_INFLIGHT 256 // макс пакетов в полёте (для контроля inflight)
#define ROUTER_ACK_INTERVAL_TB 1000 // интервал ACK: 100ms в timebase (0.1ms)
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
// Флаги в старших битах seq (взаимоисключающие)
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
struct ETCP_ROUTER_BINDINGS {
etcp_recv_fn callbacks[SVC_ROUTE_MAX_BINDINGS];
};
// Инициализация: etcp_bind(ETCP_ID_SVC_ROUTE) + очистка bindings + создание router_conns
int etcp_router_init(struct UTUN_INSTANCE* inst);
// Деинициализация
void etcp_router_destroy(struct UTUN_INSTANCE* inst);
// Зарегистрировать обработчик сервиса
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback);
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry);
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id);
// Отправить данные с авто-seq и контролем inflight
// data: payload без svc_id, flags: битовые флаги (зарезервировано)
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
// Закрыть все router_conn для указанного remote_node_id (peer умер)
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
#endif // ETCP_ROUTER_H
+343
View File
@@ -0,0 +1,343 @@
// icmp_proxy.c — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
#include "icmp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "tun_if.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
#define ICMP_DEF_TTL 64
#define ICMP_TIMEOUT_TB 50000 // 5s for echo reply
static struct icmp_proxy_ctx* g_icmp_ctx = NULL;
static void req_expire_timer_cb(void* arg);
static void req_expire(struct icmp_proxy_ctx* ctx);
static struct icmp_request* req_find_by_id(struct icmp_request* head, uint16_t echo_id, uint16_t echo_seq) {
struct icmp_request* r;
for (r = head; r; r = r->next) if (r->echo_id == echo_id && r->echo_seq == echo_seq) return r;
return NULL;
}
// Сборка и отправка raw ICMP echo запроса
static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return -1;
size_t icmp_len = ICMP_MINLEN + payload_len;
uint8_t* buf = u_malloc(icmp_len);
if (!buf) return -1;
struct icmp* icmp_hdr = (struct icmp*)buf;
memset(icmp_hdr, 0, icmp_len);
icmp_hdr->icmp_type = ICMP_ECHO;
icmp_hdr->icmp_code = 0;
icmp_hdr->icmp_id = echo_id;
icmp_hdr->icmp_seq = echo_seq;
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
icmp_hdr->icmp_cksum = 0;
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto dst=0x%08x id=0x%04x seq=%u len=%zu",
dst_ip, echo_id, echo_seq, icmp_len);
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}};
ssize_t n = sendto(g_icmp_ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr));
u_free(buf);
if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto failed: %s", strerror(errno)); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto sent %zd bytes", n);
struct icmp_request* r = u_calloc(1, sizeof(struct icmp_request));
if (!r) return 0;
r->client_node_id = client_node_id; r->dst_ip = dst_ip; r->orig_src_ip = orig_src_ip;
r->echo_id = echo_id; r->echo_seq = echo_seq;
r->payload_len = payload_len > sizeof(r->payload) ? sizeof(r->payload) : payload_len;
if (payload_len > 0) memcpy(r->payload, payload, r->payload_len);
r->sent_tb = get_time_tb();
r->next = g_icmp_ctx->pending; g_icmp_ctx->pending = r;
if (!g_icmp_ctx->expire_timer)
g_icmp_ctx->expire_timer = uasync_set_timeout(g_icmp_ctx->ua, g_icmp_ctx->request_timeout_tb, g_icmp_ctx, req_expire_timer_cb, "icmp_expire");
return 0;
}
// Чтение echo ответа из raw сокета, сопоставление по echo_id
static void raw_read_cb(socket_t sock, void* arg) {
(void)sock; (void)arg;
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return;
uint8_t buf[65536];
struct sockaddr_in from; socklen_t flen = sizeof(from);
ssize_t n = recvfrom(g_icmp_ctx->raw_sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
if (n <= 0) { if (n < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: recvfrom error: %s", strerror(errno)); return; }
if (n < (ssize_t)(sizeof(struct ip) + ICMP_MINLEN)) return;
struct ip* ip_hdr = (struct ip*)buf;
if (ip_hdr->ip_p != IPPROTO_ICMP) return;
size_t ip_hdr_len = ip_hdr->ip_hl * 4;
if (n < (ssize_t)(ip_hdr_len + ICMP_MINLEN)) return;
struct icmp* icmp_hdr = (struct icmp*)(buf + ip_hdr_len);
if (icmp_hdr->icmp_type != ICMP_ECHOREPLY) return;
struct icmp_request* r = req_find_by_id(g_icmp_ctx->pending, icmp_hdr->icmp_id, icmp_hdr->icmp_seq);
if (!r) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "icmp_proxy: unclaimed echo reply id=0x%04x seq=%u from=0x%08x",
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr); return; }
{ struct icmp_request** rp = &g_icmp_ctx->pending;
while (*rp) { if (*rp == r) { *rp = r->next; break; } rp = &(*rp)->next; } }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: echo reply id=0x%04x seq=%u from=0x%08x",
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr);
size_t payload_len = n - ip_hdr_len - ICMP_MINLEN;
if (payload_len > 1500) payload_len = 1500;
uint8_t* payload = (payload_len > 0) ? (uint8_t*)(icmp_hdr->icmp_data) : NULL;
struct ll_entry* e = queue_entry_new(0);
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[1] = ICMP_PROXY_SUBCMD_REPLY;
memcpy(e->dgram + 2, &r->client_node_id, 8);
memcpy(e->dgram + 10, &r->dst_ip, 4);
memcpy(e->dgram + 14, &r->orig_src_ip, 4);
memcpy(e->dgram + 18, &icmp_hdr->icmp_id, 2);
memcpy(e->dgram + 20, &icmp_hdr->icmp_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e);
if (ret != 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: etcp_route_send reply failed: %d", ret);
else DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply forwarded to client %016llx", (unsigned long long)r->client_node_id);
u_free(r);
}
// ====================================================================
// Exit узел: принять REQUEST, отправить echo через raw сокет
// ====================================================================
static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
if (!inst || !g_icmp_ctx || entry->len < ICMP_PROXY_HDR_SIZE + 1) goto drop;
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8);
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 10, 4);
uint32_t orig_src_ip; memcpy(&orig_src_ip, entry->dgram + 14, 4);
uint16_t echo_id; memcpy(&echo_id, entry->dgram + 18, 2);
uint16_t echo_seq; memcpy(&echo_seq, entry->dgram + 20, 2);
uint8_t* payload = entry->dgram + ICMP_PROXY_HDR_SIZE;
size_t payload_len = entry->len - ICMP_PROXY_HDR_SIZE;
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
exit_send_echo(inst, client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, payload_len);
} else if (g_icmp_ctx->test_loopback) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: test loopback reply to 0x%08x", dst_ip);
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (e->dgram) {
e->dgram[0] = ETCP_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);
memcpy(e->dgram + 14, &orig_src_ip, 4);
memcpy(e->dgram + 18, &echo_id, 2);
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
etcp_route_send(inst, client_node_id, e);
} else queue_entry_free(e);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: no raw socket, dropping echo request to 0x%08x", dst_ip);
}
drop:
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: принять 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; }
uint32_t src_ip;
uint32_t orig_src_ip;
uint16_t echo_id, echo_seq;
memcpy(&src_ip, entry->dgram + 10, 4);
memcpy(&orig_src_ip, entry->dgram + 14, 4);
memcpy(&echo_id, entry->dgram + 18, 2);
memcpy(&echo_seq, entry->dgram + 20, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: client got reply id=0x%04x seq=%u from=0x%08x dst=0x%08x",
echo_id, echo_seq, src_ip, orig_src_ip);
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
icmp_proxy_deliver_reply(inst, orig_src_ip, src_ip, echo_id, echo_seq,
entry->dgram + ICMP_PROXY_HDR_SIZE, entry->len - ICMP_PROXY_HDR_SIZE);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Единый etcp_router коллбэк
// ====================================================================
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); }
return;
}
uint8_t subcmd = entry->dgram[1];
if (subcmd == ICMP_PROXY_SUBCMD_REQUEST) { exit_handle_request(conn, entry); return; }
if (subcmd == ICMP_PROXY_SUBCMD_REPLY) { client_handle_reply(conn, entry); return; }
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: отправить ICMP echo запрос в exit узел
// ====================================================================
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!inst) return -1;
struct ll_entry* e = queue_entry_new(0);
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[1] = ICMP_PROXY_SUBCMD_REQUEST;
memcpy(e->dgram + 2, &inst->node_id, 8);
memcpy(e->dgram + 10, &dst_ip, 4);
memcpy(e->dgram + 14, &orig_src_ip, 4);
memcpy(e->dgram + 18, &echo_id, 2);
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, exit_node_id, e);
}
// ====================================================================
// Сторона клиента: доставить ICMP echo ответ в TUN
// ====================================================================
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
!inst ? "inst" : !inst->tcp_proxy ? "tcp_proxy" : "TUN");
return -1;
}
struct tun_if* tun = inst->tcp_proxy->tun;
size_t icmp_len = ICMP_MINLEN + payload_len;
size_t pkt_len = 20 + icmp_len;
uint8_t* pkt = u_malloc(pkt_len);
if (!pkt) return -1;
memset(pkt, 0, 20);
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_ICMP;
uint16_t total_len = htons(20 + icmp_len);
memcpy(pkt + 2, &total_len, 2);
memcpy(pkt + 12, &src_ip, 4);
memcpy(pkt + 16, &dst_ip, 4);
{ uint32_t ip_sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); ip_sum += w; }
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16); ip_sum += (ip_sum >> 16);
uint16_t cs = (uint16_t)(~ip_sum); memcpy(pkt + 10, &cs, 2); }
struct icmp* icmp_hdr = (struct icmp*)(pkt + 20);
icmp_hdr->icmp_type = ICMP_ECHOREPLY; icmp_hdr->icmp_code = 0;
icmp_hdr->icmp_id = echo_id; icmp_hdr->icmp_seq = echo_seq;
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
icmp_hdr->icmp_cksum = 0;
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(pkt); return -1; }
e->dgram = u_malloc(1 + pkt_len);
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len;
u_free(pkt);
queue_data_put(tun->input_queue, e);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply delivered to TUN id=0x%04x seq=%u", echo_id, echo_seq);
return 0;
}
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled) {
(void)inst;
if (g_icmp_ctx) g_icmp_ctx->test_loopback = enabled;
}
static void req_expire_timer_cb(void* arg) {
struct icmp_proxy_ctx* ctx = (struct icmp_proxy_ctx*)arg;
req_expire(ctx);
ctx->expire_timer = NULL;
if (ctx->pending)
ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire");
}
static void req_expire(struct icmp_proxy_ctx* ctx) {
uint64_t now = get_time_tb(); struct icmp_request** prev = &ctx->pending;
while (*prev) {
struct icmp_request* r = *prev;
if (now - r->sent_tb > ctx->request_timeout_tb) { *prev = r->next; u_free(r); }
else prev = &r->next;
}
}
// ====================================================================
// Публичные init/destroy
// ====================================================================
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
if (!inst) return -1;
struct icmp_proxy_ctx* ctx = u_calloc(1, sizeof(struct icmp_proxy_ctx));
if (!ctx) return -1;
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID;
ctx->request_timeout_tb = ICMP_TIMEOUT_TB;
ctx->is_exit = inst->remote_proxy.enabled;
ctx->test_loopback = 0;
ctx->expire_timer = NULL;
g_icmp_ctx = ctx;
if (ctx->is_exit) {
ctx->raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (ctx->raw_sock == SOCKET_INVALID)
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket(SOCK_RAW) failed: %s", strerror(errno));
else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket created fd=%d", ctx->raw_sock);
ctx->raw_read_id = uasync_add_socket(ua, ctx->raw_sock, raw_read_cb, NULL, NULL, NULL);
if (!ctx->raw_read_id) { socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; }
}
}
etcp_router_bind(inst, ETCP_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;
}
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !g_icmp_ctx) return;
etcp_router_unbind(inst, ETCP_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; }
socket_close_wrapper(g_icmp_ctx->raw_sock);
}
struct icmp_request* r = g_icmp_ctx->pending;
while (r) { struct icmp_request* n = r->next; u_free(r); r = n; }
u_free(g_icmp_ctx); g_icmp_ctx = NULL;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy destroyed");
}
+65
View File
@@ -0,0 +1,65 @@
// icmp_proxy.h — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
#ifndef ICMP_PROXY_H
#define ICMP_PROXY_H
#include <stdint.h>
#include "../lib/socket_compat.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
// Подкоманды
#define ICMP_PROXY_SUBCMD_REQUEST 0x01 // client→exit: пингани dst_ip
#define ICMP_PROXY_SUBCMD_REPLY 0x02 // exit→client: echo reply
// Заголовок сообщения (не включая байт svc_id)
// svc_id(1) + subcmd(1) + sender_node_id(8) + dst_ip(4) + orig_src_ip(4) + icmp_id(2) + icmp_seq(2) + payload
#define ICMP_PROXY_HDR_SIZE 22
// Активный ICMP echo запрос (сторона exit узла)
struct icmp_request {
struct icmp_request* next;
uint64_t client_node_id;
uint32_t dst_ip;
uint32_t orig_src_ip; // IP исходного отправителя (чтобы вернуть reply правильному адресату)
uint16_t echo_id;
uint16_t echo_seq;
uint8_t payload[1500];
size_t payload_len;
uint64_t sent_tb;
socket_t sock; // raw сокет (только если per-request)
};
struct icmp_proxy_ctx {
int initialized;
int is_exit;
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
socket_t raw_sock; // один SOCK_RAW для всего ICMP на exit
void* raw_read_id;
struct icmp_request* pending;
uint64_t request_timeout_tb;
int test_loopback; // 1 = виртуальный loopback без raw сокета (только тесты)
void* expire_timer; // периодический таймер очистки истёкших запросов
};
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua);
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst);
// Единый etcp_router коллбэк
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
// Сторона клиента: принять IP/ICMP echo request с TUN, отправить через etcp
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len);
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len);
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled);
#endif // ICMP_PROXY_H
+130
View File
@@ -0,0 +1,130 @@
// lwip_pbuf.c — simplified packet buffer (PBUF_RAM only)
#include "lwip_pbuf.h"
#include "../../lib/mem.h"
#include <string.h>
#define PBUF_POOL_BUFSIZE 256 // not used with PBUF_RAM only
struct pbuf* pbuf_alloc(uint16_t layer, uint16_t length)
{
uint16_t offset = (layer == PBUF_TRANSPORT) ? 54 : ((layer == PBUF_IP) ? 34 : (uint16_t)layer);
uint16_t total = sizeof(struct pbuf) + offset + length;
struct pbuf *p = u_malloc(total);
if (!p) return NULL;
p->next = NULL;
p->payload = (uint8_t*)p + sizeof(struct pbuf) + offset;
p->tot_len = length;
p->len = length;
p->ref = 1;
p->flags = 0;
p->type_internal = 0;
return p;
}
void pbuf_free(struct pbuf *p)
{
while (p) {
struct pbuf *next = p->next;
if (p->ref > 0) p->ref--;
if (p->ref == 0) u_free(p);
p = next;
}
}
void pbuf_ref(struct pbuf *p)
{
if (p) p->ref++;
}
int pbuf_header(struct pbuf *p, int16_t increment)
{
if (!p) return -1;
if (increment == 0) return 0;
if (increment < 0) {
if ((uint16_t)(-increment) > p->len) return -1;
} else {
if (p->payload < (void*)((uint8_t*)p + sizeof(struct pbuf))) return -1;
uint16_t avail = (uint16_t)((uint8_t*)p->payload - ((uint8_t*)p + sizeof(struct pbuf)));
if ((uint16_t)increment > avail) return -1;
}
p->payload = (uint8_t*)p->payload - increment;
p->len = (uint16_t)(p->len + increment);
p->tot_len = (uint16_t)(p->tot_len + increment);
return 0;
}
int pbuf_header_force(struct pbuf *p, int16_t increment)
{
if (!p) return -1;
if (increment < 0) {
if ((uint16_t)(-increment) > p->len) return -1;
}
p->payload = (uint8_t*)p->payload - increment;
p->len = (uint16_t)(p->len + increment);
p->tot_len = (uint16_t)(p->tot_len + increment);
return 0;
}
void pbuf_chain(struct pbuf *head, struct pbuf *tail)
{
if (!head) return;
struct pbuf *p = head;
while (p->next) p = p->next;
p->next = tail;
}
struct pbuf* pbuf_dechain(struct pbuf *p)
{
if (!p) return NULL;
struct pbuf *next = p->next;
p->next = NULL;
p->tot_len = p->len;
return next;
}
uint16_t pbuf_clen(const struct pbuf *p)
{
uint16_t count = 0;
while (p) { count++; p = p->next; }
return count;
}
uint16_t pbuf_copy_partial(const struct pbuf *p, void *buf, uint16_t len, uint16_t offset)
{
uint16_t copied = 0;
while (p && len > 0) {
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
uint16_t chunk = (uint16_t)(p->len - offset);
if (chunk > len) chunk = len;
memcpy((uint8_t*)buf + copied, (uint8_t*)p->payload + offset, chunk);
copied = (uint16_t)(copied + chunk); len = (uint16_t)(len - chunk); offset = 0;
p = p->next;
}
return copied;
}
int pbuf_take(struct pbuf *p, const void *data, uint16_t len)
{
return pbuf_take_at(p, data, len, 0);
}
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset)
{
uint16_t remaining = len;
const uint8_t *src = (const uint8_t*)data;
while (p && remaining > 0) {
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
uint16_t chunk = (uint16_t)(p->len - offset);
if (chunk > remaining) chunk = remaining;
memcpy((uint8_t*)p->payload + offset, src, chunk);
src += chunk; remaining = (uint16_t)(remaining - chunk); offset = 0;
p = p->next;
}
return (remaining == 0) ? 0 : -1;
}
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset)
{
if (pbuf_copy_partial(p, buf, (uint16_t)len, (uint16_t)offset) == (uint16_t)len) return buf;
return NULL;
}
+41
View File
@@ -0,0 +1,41 @@
// lwip_pbuf.h — simplified packet buffer (PBUF_RAM only)
#ifndef LWIP_PBUF_H
#define LWIP_PBUF_H
#include <stdint.h>
#include <stddef.h>
enum pbuf_layer {
PBUF_TRANSPORT = 54, // 14(ETH) + 20(IP) + 20(TCP)
PBUF_IP = 34, // 14(ETH) + 20(IP)
PBUF_LINK = 14, // 14(ETH)
PBUF_RAW_TX = 54,
PBUF_RAW = 0
};
struct pbuf {
struct pbuf *next;
void *payload;
uint16_t tot_len;
uint16_t len;
uint8_t ref;
uint8_t flags;
uint8_t type_internal;
};
#define PBUF_FLAG_TCP_FIN 0x01U
struct pbuf* pbuf_alloc(uint16_t layer, uint16_t length);
void pbuf_free(struct pbuf *p);
void pbuf_ref(struct pbuf *p);
int pbuf_header(struct pbuf *p, int16_t increment);
int pbuf_header_force(struct pbuf *p, int16_t increment);
void pbuf_chain(struct pbuf *head, struct pbuf *tail);
struct pbuf* pbuf_dechain(struct pbuf *p);
uint16_t pbuf_clen(const struct pbuf *p);
uint16_t pbuf_copy_partial(const struct pbuf *p, void *buf, uint16_t len, uint16_t offset);
int pbuf_take(struct pbuf *p, const void *data, uint16_t len);
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset);
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset);
#endif
File diff suppressed because it is too large. Load diff
+273
View File
@@ -0,0 +1,273 @@
// lwip_tcp.h — lwIP TCP public API (adapted for uTun)
#ifndef LWIP_TCP_H
#define LWIP_TCP_H
#include <stdint.h>
#include <stddef.h>
#include "lwip_pbuf.h"
struct UASYNC;
struct memory_pool;
struct tcp_seg;
struct lwip_tcp_ctx;
enum tcp_state {
CLOSED = 0,
LISTEN = 1,
SYN_SENT = 2,
SYN_RCVD = 3,
ESTABLISHED = 4,
FIN_WAIT_1 = 5,
FIN_WAIT_2 = 6,
CLOSE_WAIT = 7,
CLOSING = 8,
LAST_ACK = 9,
TIME_WAIT = 10
};
typedef uint16_t tcpwnd_size_t;
typedef uint16_t tcpflags_t;
#define TCP_WRITE_FLAG_COPY 0x01
#define TCP_WRITE_FLAG_MORE 0x02
// pcb flags
#define TF_ACK_DELAY 0x01U
#define TF_ACK_NOW 0x02U
#define TF_INFR 0x04U
#define TF_CLOSEPEND 0x08U
#define TF_RXCLOSED 0x10U
#define TF_FIN 0x20U
#define TF_NODELAY 0x40U
#define TF_NAGLEMEMERR 0x80U
#define TF_RTO 0x0100U
// input processing flags (not on pcb->flags)
#define TF_RESET 0x08U
#define TF_CLOSED 0x10U
#define TF_GOT_FIN 0x20U
// Error codes (LERR_ prefix to avoid conflicts with u_async.h and socket_compat.h)
enum tcp_err_enum {
LERR_OK = 0,
LERR_MEM = -1,
LERR_BUF = -2,
LERR_TIMEOUT = -3,
LERR_RTE = -4,
LERR_INPROGRESS = -5,
LERR_VAL = -6,
LERR_WOULDBLOCK = -7,
LERR_USE = -8,
LERR_ALREADY = -9,
LERR_ISCONN = -10,
LERR_CONN = -11,
LERR_IF = -12,
LERR_ABRT = -13,
LERR_RST = -14,
LERR_CLSD = -15,
LERR_ARG = -16
};
typedef int err_t;
// Forward declaration for callbacks
struct tcp_pcb;
// Callbacks
typedef err_t (*tcp_accept_fn) (void *arg, struct tcp_pcb *newpcb, err_t err);
typedef err_t (*tcp_recv_fn) (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
typedef err_t (*tcp_sent_fn) (void *arg, struct tcp_pcb *pcb, uint16_t len);
typedef err_t (*tcp_poll_fn) (void *arg, struct tcp_pcb *pcb);
typedef void (*tcp_err_fn) (void *arg, err_t err);
typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *pcb, err_t err);
// Output callback — called when TCP wants to send an IP packet
typedef err_t (*tcp_output_fn)(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
#define TCP_TRACE_SIZE 384
struct tcp_trace_entry {
uint32_t ticks;
char event;
uint32_t seq;
uint16_t len;
uint16_t wnd;
uint16_t rto;
int16_t rtime;
uint8_t ctx_id;
uint8_t state;
};
struct tcp_trace_buf {
struct tcp_trace_entry e[TCP_TRACE_SIZE];
int pos;
int count;
};
// PCB structures
struct tcp_pcb {
struct tcp_pcb *next;
struct lwip_tcp_ctx *ctx;
void *callback_arg;
enum tcp_state state;
uint8_t prio;
uint16_t local_port;
uint16_t remote_port;
uint32_t local_ip; // host byte order
uint32_t remote_ip; // host byte order
uint8_t ttl;
uint8_t tos;
tcpflags_t flags;
uint8_t polltmr, pollinterval;
uint8_t last_timer;
uint32_t tmr;
uint32_t rcv_nxt;
tcpwnd_size_t rcv_wnd;
tcpwnd_size_t rcv_ann_wnd;
uint32_t rcv_ann_right_edge;
int16_t rtime;
uint16_t mss;
uint32_t rttest;
uint32_t rtseq;
int16_t sa, sv;
int16_t rto;
uint8_t nrtx;
uint8_t dupacks;
uint32_t lastack;
tcpwnd_size_t cwnd;
tcpwnd_size_t ssthresh;
uint32_t rto_end;
uint32_t snd_nxt;
uint32_t snd_wl1, snd_wl2;
uint32_t snd_lbb;
tcpwnd_size_t snd_wnd;
tcpwnd_size_t snd_wnd_max;
tcpwnd_size_t snd_buf;
uint16_t snd_queuelen;
tcpwnd_size_t bytes_acked;
struct tcp_seg *unsent;
struct tcp_seg *unacked;
struct tcp_seg *ooseq;
struct pbuf *refused_data;
struct tcp_pcb_listen *listener;
tcp_sent_fn sent;
tcp_recv_fn recv;
tcp_connected_fn connected;
tcp_poll_fn poll;
tcp_err_fn errf;
uint32_t keep_idle;
uint8_t persist_cnt;
uint8_t persist_backoff;
uint8_t persist_probe;
uint8_t keep_cnt_sent;
};
struct tcp_pcb_listen {
struct tcp_pcb *next;
struct lwip_tcp_ctx *ctx;
void *callback_arg;
enum tcp_state state;
uint8_t prio;
uint16_t local_port;
uint32_t local_ip;
tcp_accept_fn accept;
};
// Context for the TCP module (one per tcp_proxy instance)
struct lwip_tcp_ctx {
struct UASYNC *ua;
tcp_output_fn output;
void *output_arg;
void *timer;
struct tcp_pcb *bound_pcbs;
struct tcp_pcb *listen_pcbs;
struct tcp_pcb *active_pcbs;
struct tcp_pcb *tw_pcbs;
struct memory_pool *pcb_pool;
struct memory_pool *pcb_listen_pool;
struct memory_pool *seg_pool;
uint16_t tmr_interval_ms; // default 250 (was TCP_TMR_INTERVAL)
uint16_t rto_min_ms; // default 3000 (initial RTO in ms)
uint16_t rto_max_ms; // default 0 (0=unlimited, cap in ms)
uint32_t ticks;
uint8_t slowtmr_ctr; // for last_timer check (incremented by fasttmr and slowtmr)
uint8_t tmr_phase; // for even/odd slowtmr decision (incremented in tcp_tmr_cb)
uint16_t iss_seed;
uint16_t ip_id;
uint16_t port_seed;
char trace_id; // 'A' or 'B' for trace dump identification
struct tcp_trace_buf trace;
struct {
uint32_t pbuf_fails; // pbuf_alloc → NULL
uint32_t seg_fails; // tcp_create_segment → NULL (seg pool)
uint32_t pcb_fails; // tcp_alloc → NULL (pcb pool)
uint32_t pcb_listen_fails; // tcp_listen pool alloc fail
uint32_t write_fails; // tcp_write → ERR_MEM
uint32_t enq_fails; // tcp_enqueue_flags → ERR_MEM
uint32_t rst_fails; // tcp_rst pbuf alloc fail
uint32_t ack_fails; // tcp_send_empty_ack pbuf fail
uint32_t probe_fails; // tcp_zero_window_probe pbuf fail
uint32_t split_fails; // tcp_split_unsent_seg fail
} stats;
};
// Lifecycle
struct lwip_tcp_ctx* lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void *output_arg);
void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx);
void lwip_tcp_set_timer(struct lwip_tcp_ctx *ctx,
uint16_t interval_ms, uint16_t rto_min_ms, uint16_t rto_max_ms);
// Input: feed an IP packet payload (after IP header) to TCP
void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
uint32_t src_ip, uint32_t dst_ip);
// PCB operations
struct tcp_pcb* tcp_new(struct lwip_tcp_ctx *ctx);
err_t tcp_bind(struct tcp_pcb *pcb, uint32_t ip, uint16_t port);
struct tcp_pcb* tcp_listen(struct tcp_pcb *pcb);
err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ip, uint16_t port, tcp_connected_fn connected);
err_t tcp_write(struct tcp_pcb *pcb, const void *data, uint16_t len, uint8_t apiflags);
err_t tcp_output(struct tcp_pcb *pcb);
err_t tcp_close(struct tcp_pcb *pcb);
void tcp_abort(struct tcp_pcb *pcb);
void tcp_recved(struct tcp_pcb *pcb, uint16_t len);
// Callback setters
void tcp_arg (struct tcp_pcb *pcb, void *arg);
void tcp_recv (struct tcp_pcb *pcb, tcp_recv_fn recv);
void tcp_sent (struct tcp_pcb *pcb, tcp_sent_fn sent);
void tcp_err (struct tcp_pcb *pcb, tcp_err_fn errf);
void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept);
void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval);
// Helpers
#define tcp_mss(pcb) ((pcb)->mss)
#define tcp_sndbuf(pcb) ((pcb)->snd_buf)
#define tcp_sndqueuelen(pcb) ((pcb)->snd_queuelen)
#define tcp_nagle_disable(pcb) ((pcb)->flags |= TF_NODELAY)
#define tcp_nagle_enable(pcb) ((pcb)->flags &= (tcpflags_t)~TF_NODELAY)
#define tcp_nagle_disabled(pcb) (((pcb)->flags & TF_NODELAY) != 0)
#define tcp_set_flags(pcb, set_flags) ((pcb)->flags |= (tcpflags_t)(set_flags))
#define tcp_clear_flags(pcb, clr_flags) ((pcb)->flags &= (tcpflags_t)~(clr_flags))
#define tcp_is_flag_set(pcb, flag) (((pcb)->flags & (flag)) != 0)
#endif
File diff suppressed because it is too large. Load diff
+49
View File
@@ -0,0 +1,49 @@
// lwip_tcp_opts.h — hardcoded TCP configuration (no #ifdefs)
#ifndef LWIP_TCP_OPTS_H
#define LWIP_TCP_OPTS_H
#define TCP_MSS 1460
#define TCP_SND_BUF (16 * TCP_MSS)
#define TCP_WND (8 * TCP_MSS)
#define TCP_SND_QUEUELEN (4 * TCP_SND_BUF / TCP_MSS)
#define TCP_SNDLOWAT (2 * TCP_MSS < TCP_SND_BUF / 2 ? 2 * TCP_MSS : TCP_SND_BUF / 2)
#define TCP_MAXRTX 12
#define TCP_SYNMAXRTX 6
#define TCP_TMR_INTERVAL 250 // ms for tcp_tmr
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
#define TCP_FIN_WAIT_TIMEOUT 20000 // ms
#define TCP_SYN_RCVD_TIMEOUT 20000 // ms
#define TCP_MSL 60000 // ms
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
#define TCP_KEEPINTVL_DEFAULT 75000
#define TCP_KEEPCNT_DEFAULT 9
#define TCP_WND_SCALE 0 // disabled
#define TCP_TIMESTAMPS 0 // disabled
#define TCP_SACK_OUT 0 // disabled
#define TCP_QUEUE_OOSEQ 1
#define TCP_OVERSIZE 0
#define TCP_LISTEN_BACKLOG 0
#define TCP_KEEPALIVE 0
#define TCP_CHECKSUM_ON_COPY 0
#define TCP_CALCULATE_EFF_SEND_MSS 0
#define TCP_LOCAL_PORT_RANGE_START 0xc000
#define TCP_LOCAL_PORT_RANGE_END 0xffff
#if TCP_MSS > 536
#define INITIAL_MSS 536
#else
#define INITIAL_MSS TCP_MSS
#endif
#define TCP_PRIO_MIN 1
#define TCP_PRIO_NORMAL 64
#define TCP_PRIO_MAX 127
#endif
File diff suppressed because it is too large. Load diff
+250
View File
@@ -0,0 +1,250 @@
// lwip_tcp_priv.h — lwIP TCP internal API
#ifndef LWIP_TCP_PRIV_H
#define LWIP_TCP_PRIV_H
#include "lwip_tcp.h"
#include <string.h>
// Packed struct support
#ifndef PACK_STRUCT_BEGIN
#define PACK_STRUCT_BEGIN
#endif
#ifndef PACK_STRUCT_END
#define PACK_STRUCT_END
#endif
#ifndef PACK_STRUCT_FIELD
#define PACK_STRUCT_FIELD(x) x
#endif
#ifndef PACK_STRUCT_STRUCT
#if defined(__GNUC__) || defined(__clang__)
#define PACK_STRUCT_STRUCT __attribute__((packed))
#else
#define PACK_STRUCT_STRUCT
#endif
#endif
#ifndef _WIN32
#include <arpa/inet.h>
#else
#define htons(x) ((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF))
#define ntohs(x) htons(x)
#define htonl(x) ((((x) & 0xFF) << 24) | (((x) & 0xFF00) << 8) | (((x) >> 8) & 0xFF00) | (((x) >> 24) & 0xFF))
#define ntohl(x) htonl(x)
#endif
// TCP header (wire format)
PACK_STRUCT_BEGIN
struct tcp_hdr {
PACK_STRUCT_FIELD(uint16_t src);
PACK_STRUCT_FIELD(uint16_t dest);
PACK_STRUCT_FIELD(uint32_t seqno);
PACK_STRUCT_FIELD(uint32_t ackno);
PACK_STRUCT_FIELD(uint16_t _hdrlen_rsvd_flags);
PACK_STRUCT_FIELD(uint16_t wnd);
PACK_STRUCT_FIELD(uint16_t chksum);
PACK_STRUCT_FIELD(uint16_t urgp);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define TCP_FIN 0x01U
#define TCP_SYN 0x02U
#define TCP_RST 0x04U
#define TCP_PSH 0x08U
#define TCP_ACK 0x10U
#define TCP_URG 0x20U
#define TCP_ECE 0x40U
#define TCP_CWR 0x80U
#define TCP_FLAGS 0x3fU
#define TCPH_HDRLEN(phdr) ((uint16_t)(ntohs((phdr)->_hdrlen_rsvd_flags) >> 12))
#define TCPH_HDRLEN_BYTES(phdr) ((uint8_t)(TCPH_HDRLEN(phdr) << 2))
#define TCPH_FLAGS(phdr) ((uint8_t)(ntohs((phdr)->_hdrlen_rsvd_flags) & TCP_FLAGS))
#define TCPH_HDRLEN_SET(phdr, len) (phdr)->_hdrlen_rsvd_flags = htons(((len) << 12) | TCPH_FLAGS(phdr))
#define TCPH_FLAGS_SET(phdr, flags) (phdr)->_hdrlen_rsvd_flags = (((phdr)->_hdrlen_rsvd_flags & htons((uint16_t)~TCP_FLAGS)) | htons(flags))
#define TCPH_HDRLEN_FLAGS_SET(phdr, len, flags) (phdr)->_hdrlen_rsvd_flags = htons((uint16_t)(((uint16_t)(len) << 12) | (flags)))
#define TCPH_SET_FLAG(phdr, flags) (phdr)->_hdrlen_rsvd_flags = ((phdr)->_hdrlen_rsvd_flags | htons(flags))
#define TCPH_UNSET_FLAG(phdr, flags) (phdr)->_hdrlen_rsvd_flags = ((phdr)->_hdrlen_rsvd_flags & ~htons(flags))
// TCP header length
#define TCP_HLEN 20
// Sequence number macros
#define TCP_SEQ_LT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) < 0)
#define TCP_SEQ_LEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) <= 0)
#define TCP_SEQ_GT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) > 0)
#define TCP_SEQ_GEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) >= 0)
#define TCP_SEQ_BETWEEN(a,b,c) (TCP_SEQ_GEQ(a,b) && TCP_SEQ_LEQ(a,c))
// TCP options
#define TCP_OPT_END 0
#define TCP_OPT_NOP 1
#define TCP_OPT_MSS 2
#define TCP_OPT_MSS_LEN 4
// tcp_seg flags
#define TF_SEG_OPTS_MSS 0x01U
// tcp_seg structure
struct tcp_seg {
struct tcp_seg *next;
struct pbuf *p;
uint16_t len;
uint8_t flags;
struct tcp_hdr *tcphdr;
};
#define TCP_TCPLEN(seg) ((seg)->len + (((TCPH_FLAGS((seg)->tcphdr) & (TCP_FIN | TCP_SYN)) != 0) ? 1U : 0U))
// Window macros
#define TCPWND16(x) (x)
#define TCP_WND_MAX(pcb) TCP_WND
#define RCV_WND_SCALE(pcb, wnd) (wnd)
#define SND_WND_SCALE(pcb, wnd) (wnd)
#define TCP_WND_INC(wnd, inc) do { \
if ((tcpwnd_size_t)(wnd + inc) >= wnd) { wnd = (tcpwnd_size_t)(wnd + inc); } \
else { wnd = (tcpwnd_size_t)-1; } \
} while(0)
// TCP_STATE_IS_CLOSING
#define TCP_STATE_IS_CLOSING(state) ((state) >= FIN_WAIT_1)
// Nagle
#define tcp_do_output_nagle(tpcb) ((((tpcb)->unacked == NULL) || \
((tpcb)->flags & (TF_NODELAY | TF_INFR)) || \
(((tpcb)->unsent != NULL) && (((tpcb)->unsent->next != NULL) || \
((tpcb)->unsent->len >= (tpcb)->mss))) || \
(tcp_sndbuf(tpcb) == 0 || tcp_sndqueuelen(tpcb) >= TCP_SND_QUEUELEN)) ? 1 : 0)
#define tcp_output_nagle(tpcb) (tcp_do_output_nagle(tpcb) ? tcp_output(tpcb) : LERR_OK)
// Callback event macros
#define TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret) \
do { if((lpcb)->accept != NULL) (ret) = (lpcb)->accept((arg),(pcb),(err)); else (ret) = LERR_ARG; } while(0)
#define TCP_EVENT_SENT(pcb,space,ret) \
do { if((pcb)->sent != NULL) (ret) = (pcb)->sent((pcb)->callback_arg,(pcb),(space)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_RECV(pcb,p,err,ret) \
do { if((pcb)->recv != NULL) { (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err)); } \
else { (ret) = LERR_OK; if(p) pbuf_free(p); } } while(0)
#define TCP_EVENT_CLOSED(pcb,ret) \
do { if(((pcb)->recv != NULL)) { (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),NULL,LERR_OK); } \
else { (ret) = LERR_OK; } } while(0)
#define TCP_EVENT_CONNECTED(pcb,err,ret) \
do { if((pcb)->connected != NULL) (ret) = (pcb)->connected((pcb)->callback_arg,(pcb),(err)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_POLL(pcb,ret) \
do { if((pcb)->poll != NULL) (ret) = (pcb)->poll((pcb)->callback_arg,(pcb)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_ERR(last_state,errf,arg,err) \
do { (void)(last_state); if((errf) != NULL) (errf)((arg),(err)); } while(0)
// tcp_ack / tcp_ack_now
#define tcp_ack(pcb) do { \
if((pcb)->flags & TF_ACK_DELAY) { tcp_clear_flags(pcb, TF_ACK_DELAY); tcp_ack_now(pcb); } \
else { tcp_set_flags(pcb, TF_ACK_DELAY); } \
} while(0)
#define tcp_ack_now(pcb) tcp_set_flags(pcb, TF_ACK_NOW)
// TCP timer intervals
#define TCP_TMR_INTERVAL_MS TCP_TMR_INTERVAL
#define TCP_FAST_INTERVAL_MS TCP_FAST_INTERVAL
#define TCP_SLOW_INTERVAL_MS TCP_SLOW_INTERVAL
#define TCP_FIN_WAIT_TIMEOUT_MS TCP_FIN_WAIT_TIMEOUT
#define TCP_SYN_RCVD_TIMEOUT_MS TCP_SYN_RCVD_TIMEOUT
#define TCP_MSL_MS TCP_MSL
// build MSS option
#define TCP_BUILD_MSS_OPTION(mss) htonl(0x02040000 | ((mss) & 0xFFFF))
// IP header
PACK_STRUCT_BEGIN
struct ip_hdr {
uint8_t vhl;
uint8_t tos;
uint16_t len;
uint16_t id;
uint16_t offset;
uint8_t ttl;
uint8_t proto;
uint16_t chksum;
uint32_t src;
uint32_t dst;
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define IP_PROTO_TCP 6
// TCP backoff arrays
extern const uint8_t tcp_backoff[13];
extern const uint8_t tcp_persist_backoff[7];
// Internal functions
err_t tcp_send_empty_ack(struct tcp_pcb *pcb);
err_t tcp_send_fin(struct tcp_pcb *pcb);
err_t tcp_enqueue_flags(struct tcp_pcb *pcb, uint8_t flags);
void tcp_rst(struct tcp_pcb *pcb, uint32_t seqno, uint32_t ackno,
uint32_t local_ip, uint32_t remote_ip,
uint16_t local_port, uint16_t remote_port);
err_t tcp_rexmit(struct tcp_pcb *pcb);
err_t tcp_rexmit_rto_prepare(struct tcp_pcb *pcb);
void tcp_rexmit_rto_commit(struct tcp_pcb *pcb);
void tcp_rexmit_rto(struct tcp_pcb *pcb);
void tcp_rexmit_fast(struct tcp_pcb *pcb);
void tcp_rexmit_seg(struct tcp_pcb *pcb, struct tcp_seg *seg);
err_t tcp_keepalive(struct tcp_pcb *pcb);
err_t tcp_zero_window_probe(struct tcp_pcb *pcb);
err_t tcp_split_unsent_seg(struct tcp_pcb *pcb, uint16_t split);
void tcp_trigger_input_pcb_close(void);
err_t tcp_process_refused_data(struct tcp_pcb *pcb);
// Segment management
struct tcp_pcb* tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio);
void tcp_free(struct tcp_pcb *pcb);
void tcp_pcb_purge(struct tcp_pcb *pcb);
void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb);
void tcp_segs_free(struct tcp_seg *seg);
void tcp_seg_free(struct tcp_seg *seg);
struct tcp_seg* tcp_seg_copy(struct tcp_seg *seg);
void tcp_free_ooseq(struct tcp_pcb *pcb);
// Timer internal
void tcp_fasttmr(struct lwip_tcp_ctx *ctx);
void tcp_slowtmr(struct lwip_tcp_ctx *ctx);
void tcp_txnow(struct lwip_tcp_ctx *ctx);
// Utils
uint32_t tcp_next_iss(struct tcp_pcb *pcb);
uint16_t tcp_checksum(const void *data, uint16_t len);
uint16_t tcp_ip_checksum(const struct ip_hdr *iph);
// Trace hook — set by test to capture internal TCP events (ring buffer)
typedef void (*tcp_trace_fn)(struct lwip_tcp_ctx *ctx, char event, uint32_t seq, uint16_t len, uint16_t wnd, const char *msg);
extern tcp_trace_fn g_tcp_trace;
void lwip_tcp_trace_record(struct lwip_tcp_ctx *ctx, char event, uint32_t seq,
uint16_t len, uint16_t wnd, uint16_t rto, int16_t rtime, uint8_t state);
void lwip_tcp_trace_dump(struct lwip_tcp_ctx *ctx);
void lwip_tcp_trace_clear(struct lwip_tcp_ctx *ctx);
void lwip_tcp_stats_dump(struct lwip_tcp_ctx *ctx);
void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
// PCB list management
#define TCP_REG(pcbs, npcb) do { \
(npcb)->next = *(pcbs); *(pcbs) = (npcb); \
} while(0)
#define TCP_RMV(pcbs, npcb) do { \
if(*(pcbs) == (npcb)) { *(pcbs) = (*pcbs)->next; } \
else { struct tcp_pcb *tmp; for(tmp = *(pcbs); tmp != NULL; tmp = tmp->next) { if(tmp->next == (npcb)) { tmp->next = (npcb)->next; break; } } } \
(npcb)->next = NULL; \
} while(0)
#define TCP_REG_ACTIVE(ctx, npcb) TCP_REG(&(ctx)->active_pcbs, npcb)
#define TCP_RMV_ACTIVE(ctx, npcb) TCP_RMV(&(ctx)->active_pcbs, npcb)
#endif
+211
View File
@@ -0,0 +1,211 @@
#include "nat_transport.h"
#include "eim_nat.h"
#include "etcp.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "tun_if.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
#include <string.h>
#define NAT_SVC_HDR_SIZE 9 // svc_id(1) + src_node_id(8)
static ip_str_t ip_host_to_str(uint32_t ip_host) {
struct in_addr a; a.s_addr = htonl(ip_host);
return ip_to_str(&a, AF_INET);
}
// ==================== Callbacks ====================
// CLIENT: NAT TUN output → encapsulate in ETCP_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; }
struct nat_transport_ctx* tr = &inst->nat_tr;
struct ll_entry* pkt = queue_data_get(q);
if (!pkt) { queue_resume_callback(q); return; }
if (!pkt->dgram || pkt->len < 2) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
size_t ip_len = pkt->len - 1;
size_t total_len = NAT_SVC_HDR_SIZE + ip_len;
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;
memcpy(new_dgram + 1, &tr->self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
struct ll_entry* new_entry = queue_entry_new(0);
if (!new_entry) { u_free(new_dgram); queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_entry->dgram = new_dgram;
new_entry->len = total_len;
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry);
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);
}
}
// PROVIDER: NAT TUN output (internet response) → ingress NAT → send back via etcp_router
static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
if (!inst) { queue_resume_callback(q); return; }
struct eim_nat_ctx* ctx = &inst->nat;
struct ll_entry* pkt = queue_data_get(q);
if (!pkt) { queue_resume_callback(q); return; }
if (!pkt->dgram || pkt->len < 2) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
struct eim_nat_entry* entry = NULL;
int ret = eim_nat_ingress(ctx, pkt->dgram + 1, pkt->len - 1, &entry);
if (ret != 0 || !entry || entry->src_node_id == 0) {
if (ret == 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "Ingress NAT: no matching entry, dropping");
queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q);
return;
}
size_t ip_len = pkt->len - 1;
size_t total_len = NAT_SVC_HDR_SIZE + ip_len;
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;
memcpy(new_dgram + 1, &inst->nat_tr.self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
struct ll_entry* new_entry = queue_entry_new(0);
if (!new_entry) { u_free(new_dgram); queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_entry->dgram = new_dgram;
new_entry->len = total_len;
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry);
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);
}
}
// ETCP_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!conn || !entry || !entry->dgram || entry->len < NAT_SVC_HDR_SIZE) {
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
return;
}
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) { queue_entry_free(entry); queue_dgram_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; }
uint64_t src_node_id;
memcpy(&src_node_id, entry->dgram + 1, 8);
uint8_t* ip_data = entry->dgram + NAT_SVC_HDR_SIZE;
size_t ip_len = entry->len - NAT_SVC_HDR_SIZE;
if (tr->nat_via_node_id != 0) {
if (tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + NAT_SVC_HDR_SIZE - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT client: received %zu bytes from provider", ip_len);
}
} else {
int ret = eim_nat_egress(ctx, ip_data, ip_len, src_node_id, conn);
if (ret < 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "Egress NAT failed");
} else if (ret == 0 && tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + NAT_SVC_HDR_SIZE - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: sent %zu bytes to internet", ip_len);
}
}
queue_dgram_free(entry); queue_entry_free(entry);
}
// ==================== Init / Destroy ====================
int nat_transport_init(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->config) return -1;
struct nat_transport_ctx* tr = &inst->nat_tr;
struct global_config* g = &inst->config->global;
memset(tr, 0, sizeof(*tr));
if (!g->nat_enabled) return 0;
tr->self_node_id = inst->node_id;
tr->nat_via_node_id = g->nat_via_node_id;
if (tr->nat_via_node_id == 0) {
if (eim_nat_init_ctx(&inst->nat, g) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to init NAT engine");
return -1;
}
} else {
inst->nat.initialized = 1;
}
const char* tun_name = g->nat_tun_ifname[0] ? g->nat_tun_ifname : "tun_nat";
char ip_str[64] = "";
if (g->nat_tun_ip.family == AF_INET) {
snprintf(ip_str, sizeof(ip_str), "%s", ip_to_str(&g->nat_tun_ip.addr.v4, AF_INET).str);
} else if (g->tun_ip.family == AF_INET) {
snprintf(ip_str, sizeof(ip_str), "%s", ip_to_str(&g->tun_ip.addr.v4, AF_INET).str);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No NAT TUN IP");
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
tr->nat_tun = tun_init_nat(inst->ua, tun_name, ip_str, g->mtu, g->tun_test_mode);
if (!tr->nat_tun) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to create NAT TUN %s ip=%s", tun_name, ip_str);
eim_nat_destroy_ctx(&inst->nat);
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");
tun_close(tr->nat_tun); tr->nat_tun = NULL;
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
struct eim_nat_ctx* ctx = &inst->nat;
if (tr->nat_via_node_id != 0) {
if (tr->nat_tun->output_queue) queue_set_callback(tr->nat_tun->output_queue, nat_transport_client_tun_out_cb, inst);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT client via node %016llx, TUN=%s gw=%s ports=%u-%u",
(unsigned long long)tr->nat_via_node_id, tun_name,
ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
} else {
if (tr->nat_tun->output_queue) queue_set_callback(tr->nat_tun->output_queue, nat_transport_provider_tun_out_cb, inst);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT provider TUN=%s gw=%s ports=%u-%u",
tun_name, ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
}
tr->initialized = 1;
return 0;
}
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);
if (tr->nat_tun) { tun_close(tr->nat_tun); tr->nat_tun = NULL; }
eim_nat_destroy_ctx(&inst->nat);
memset(tr, 0, sizeof(*tr));
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT transport destroyed");
}
+22
View File
@@ -0,0 +1,22 @@
// nat_transport.h — NAT transport layer (TUN + ETCP protocol handling)
#ifndef NAT_TRANSPORT_H
#define NAT_TRANSPORT_H
#include <stdint.h>
struct tun_if;
struct ETCP_CONN;
struct nat_transport_ctx {
uint64_t self_node_id;
uint64_t nat_via_node_id;
struct tun_if* nat_tun;
int initialized;
};
struct UTUN_INSTANCE;
int nat_transport_init(struct UTUN_INSTANCE* inst);
void nat_transport_destroy(struct UTUN_INSTANCE* inst);
#endif
+10 -14
View File
@@ -30,7 +30,6 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
struct PKTNORM* pn = u_calloc(1, sizeof(struct PKTNORM));
if (!pn) {
pn->alloc_errors++;
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: calloc failed");
return NULL;
}
@@ -40,17 +39,22 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->etcp = etcp;
pn->ua = etcp->instance->ua;
pn->frag_size = etcp->mtu - 100; // Use MTU as fixed packet size (adjust if headers need subtraction)
pn->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
int min_frag = UDP_HDR_SIZE + UDP_SC_HDR_SIZE + ACK_REZERV;
if (etcp->mtu < min_frag || pn->frag_size > etcp->mtu) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: MTU %d too small (min %d)", etcp->mtu, min_frag);
pn->frag_size = etcp->mtu;
}
pn->tx_wait_time = 10;
pn->input = queue_new(pn->ua, 0, "pn_input"); // No hash needed
pn->input = queue_new(pn->ua, 0, 0, 0, "pn_input"); // No hash needed
if (!pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: queue_new(input) failed");
pn_deinit(pn);
return NULL;
}
pn->output = queue_new(pn->ua, 0, "pn_output"); // No hash needed
pn->output = queue_new(pn->ua, 0, 0, 0, "pn_output"); // No hash needed
if (!pn->output) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: queue_new(output) failed");
pn_deinit(pn);
@@ -169,17 +173,8 @@ void pn_reset(struct PKTNORM* pn) {
// Reset unpacker state
pn_unpacker_reset_state(pn);
// Drain input and output queues to discard pending data and maintain consistency
struct ll_entry* entry;
while ((entry = queue_data_get(pn->input)) != NULL) {
queue_dgram_free(entry);
queue_entry_free(entry);
}
while ((entry = queue_data_get(pn->output)) != NULL) {
queue_dgram_free(entry);
queue_entry_free(entry);
}
queue_resume_callback(pn->input);
queue_resume_callback(pn->output);
}
// Send data to packer (copies and adds to input queue or pending, triggering callback) используется только в юниттесте
@@ -307,6 +302,7 @@ static void etcp_input_ready_cb(struct ll_queue* q, void* arg) {
pn->data_ptr += remain;
in_ptr += remain;
pn_buf_renew(pn);
if (!pn->data) goto exit;
}
exit:
+387
View File
@@ -0,0 +1,387 @@
// remote_proxy.c — Удаленный TCP прокси (exit node)
#include "remote_proxy.h"
#include "tcp_proxy.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static struct remote_proxy_ctx* g_rp_ctx = NULL;
static void rp_sock_read_cb(socket_t sock, void* arg);
static void rp_sock_write_cb(socket_t sock, void* arg);
static void rp_sock_error_cb(socket_t sock, void* arg);
static void rp_sock_retry_cb(void* arg);
static void rp_sock_pause_cb(void* arg);
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len);
void rp_conn_free(struct remote_proxy_conn* rc);
#define RP_NORMALIZER_Q_THRESHOLD 64
static int rp_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rp_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, "rp_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[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, dst, e);
}
static int rp_send_connected(struct UTUN_INSTANCE* inst, uint64_t dst, uint32_t sid,
uint16_t local_port, uint8_t status) {
uint8_t buf[3];
memcpy(buf, &local_port, 2); buf[2] = status;
return rp_send_msg(inst, dst, TCP_PROXY_SUBCMD_CONNECTED, sid, buf, 3);
}
static int rp_conn_total(struct remote_proxy_conn* rc) {
if (!rc || !rc->ctx) return 0;
int n = 0; struct remote_proxy_conn* c;
for (c = rc->ctx->conns; c; c = c->next) n++;
return n;
}
// ====================================================================
// Неблокирующий send с EAGAIN и таймером повтора
// ====================================================================
static int rp_sock_try_send(struct remote_proxy_conn* rc) {
if (!rc || rc->sock == SOCKET_INVALID || !rc->out_buf) return 0;
while (rc->out_off < rc->out_len) {
ssize_t n = send(rc->sock, rc->out_buf + rc->out_off, rc->out_len - rc->out_off, MSG_NOSIGNAL);
if (n > 0) {
rc->out_off += n;
if (rc->out_off >= rc->out_len) {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
return 0;
}
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND_ERR fd=%d sid=%08x err=%s", (int)rc->sock, rc->stream_id, strerror(errno));
return -1;
}
return 0;
}
static void rp_sock_retry_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || !rc->out_buf) { rc->out_timer = NULL; return; }
int ret = rp_sock_try_send(rc);
if (ret == 0) {
rc->out_timer = NULL; rc->out_backoff = 0;
} else if (ret == 1) {
rc->out_backoff = rc->out_backoff < 20 ? 20 : rc->out_backoff * 2;
if (rc->out_backoff > 5000) rc->out_backoff = 5000;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
rc->out_timer = NULL; rc->out_backoff = 0;
}
}
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len) {
if (!rc || !data || len == 0) return;
if (rc->out_buf) {
size_t new_len = rc->out_len + len;
uint8_t* new_buf = u_realloc(rc->out_buf, new_len);
if (!new_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND realloc(%zu) failed sid=%08x", new_len, rc->stream_id); return; }
memcpy(new_buf + rc->out_len, data, len);
rc->out_buf = new_buf; rc->out_len = new_len;
return;
}
rc->out_buf = u_malloc(len);
if (!rc->out_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND malloc(%zu) failed sid=%08x", len, rc->stream_id); return; }
memcpy(rc->out_buf, data, len); rc->out_len = len; rc->out_off = 0;
int ret = rp_sock_try_send(rc);
if (ret == 0) return;
if (ret == 1) {
rc->out_backoff = 10;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
}
}
static void rp_sock_pause_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) { rc->pause_timer = NULL; return; }
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
int qcnt = -1;
if (inst) qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
rc->pause_backoff = rc->pause_backoff * 2;
if (rc->pause_backoff > 5000) rc->pause_backoff = 5000;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
if (rc->pause_buf && rc->pause_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH fd=%d sid=%08x len=%zu qcnt=%d", (int)rc->sock, rc->stream_id, rc->pause_len, qcnt);
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->pause_buf, rc->pause_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0; rp_conn_free(rc); return;
}
u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0;
}
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
rc->pause_timer = NULL;
}
// ====================================================================
// Socket callbacks
// ====================================================================
static void rp_sock_read_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
uint8_t buf[8192]; ssize_t n = recv(rc->sock, buf, sizeof(buf), 0);
if (n > 0) {
if (rc->cli_closed || rc->error) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x — inst is NULL, drop", (int)rc->sock, rc->stream_id); return; }
int qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE fd=%d sid=%08x len=%zd qcnt=%d — normalizer переполнен, пауза", (int)rc->sock, rc->stream_id, n, qcnt);
rc->pause_buf = u_malloc(n);
if (rc->pause_buf) { memcpy(rc->pause_buf, buf, n); rc->pause_len = n; }
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE malloc(%zd) failed sid=%08x", n, rc->stream_id);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
rc->pause_backoff = 50;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%zd total=%d", (int)rc->sock, rc->stream_id, n, rp_conn_total(rc));
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, buf, (size_t)n) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; rp_conn_free(rc); return;
}
} else if (n == 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:EOF fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d",
(int)rc->sock, rc->stream_id, rp_conn_total(rc), rc->cli_closed, rc->sock_closed);
rc->sock_closed = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:EOF fd=%d sid=%08x — inst is NULL, can't send CLOSE", (int)rc->sock, rc->stream_id);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
if (rc->cli_closed) rp_conn_free(rc);
} else if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x errno=%d %s total=%d",
(int)rc->sock, rc->stream_id, errno, strerror(errno), rp_conn_total(rc));
rc->error = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x — inst is NULL, can't send ERROR", (int)rc->sock, rc->stream_id);
rp_conn_free(rc);
}
}
static void rp_sock_write_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID || rc->connected) return;
int err = 0; socklen_t len = sizeof(err);
if (getsockopt(rc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
rc->connected = 1;
struct sockaddr_in local; socklen_t llen = sizeof(local);
uint16_t local_port = 0;
if (getsockname(rc->sock, (struct sockaddr*)&local, &llen) == 0) local_port = local.sin_port;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CONN fd=%d sid=%08x local=%d dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, rc->stream_id, ntohs(local_port),
rc->dest_ip[0], rc->dest_ip[1], rc->dest_ip[2], rc->dest_ip[3], ntohs(rc->dest_port),
rp_conn_total(rc));
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, local_port, TCP_PROXY_CONNECTED_OK);
if (rc->pending_buf && rc->pending_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSH fd=%d sid=%08x len=%zu", (int)rc->sock, rc->stream_id, rc->pending_len);
rp_sock_send(rc, rc->pending_buf, rc->pending_len);
u_free(rc->pending_buf); rc->pending_buf = NULL; rc->pending_len = 0;
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FAIL fd=%d sid=%08x err=%d %s",
(int)rc->sock, rc->stream_id, err, err ? strerror(err) : "unknown");
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
rp_conn_free(rc);
}
}
static void rp_sock_error_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERR fd=%d sid=%08x", (int)rc->sock, rc->stream_id);
rc->error = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERR fd=%d sid=%08x — inst is NULL", (int)rc->sock, rc->stream_id);
rp_conn_free(rc);
}
void rp_conn_free(struct remote_proxy_conn* rc) {
if (!rc) return;
int total = rp_conn_total(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d error=%d",
(int)rc->sock, rc->stream_id, total, rc->cli_closed, rc->sock_closed, rc->error);
if (rc->ctx) {
struct remote_proxy_conn** prev = &rc->ctx->conns;
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
}
if (rc->sock != SOCKET_INVALID) {
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
socket_close_wrapper(rc->sock); rc->sock = SOCKET_INVALID;
}
if (rc->pending_buf) { u_free(rc->pending_buf); rc->pending_buf = NULL; }
if (rc->out_timer) { uasync_cancel_timeout(rc->ua, rc->out_timer); rc->out_timer = NULL; }
if (rc->out_buf) { u_free(rc->out_buf); rc->out_buf = NULL; }
if (rc->pause_timer) { uasync_cancel_timeout(rc->ua, rc->pause_timer); rc->pause_timer = NULL; }
if (rc->pause_buf) { u_free(rc->pause_buf); rc->pause_buf = NULL; }
u_free(rc);
}
// ====================================================================
// Public API
// ====================================================================
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id) {
struct remote_proxy_conn* c;
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
return NULL;
}
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id) {
if (!inst || !inst->remote_proxy.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
uint8_t* dest_ip = entry->dgram + TCP_PROXY_HDR_SIZE;
uint16_t dest_port = 0; memcpy(&dest_port, dest_ip + 4, 2);
struct remote_proxy_conn* rc = u_calloc(1, sizeof(struct remote_proxy_conn));
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: u_calloc failed for sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->ctx = ctx; rc->stream_id = stream_id; rc->peer_node_id = src_node_id;
memcpy(rc->dest_ip, dest_ip, 4); rc->dest_port = dest_port;
rc->ua = inst->ua; rc->sock = SOCKET_INVALID;
rc->sock = socket(AF_INET, SOCK_STREAM, 0);
if (rc->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(rc->sock);
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
if (!rc->read_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: uasync_add_socket_t failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, dest_ip, 4); addr.sin_port = dest_port;
int ret = connect(rc->sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: connect() to %d.%d.%d.%d:%d failed: %s",
dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], ntohs(dest_port), strerror(errno));
rp_send_connected(inst, src_node_id, stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
uasync_remove_socket_t(rc->ua, rc->sock); socket_close_wrapper(rc->sock); u_free(rc);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
rc->next = ctx->conns; ctx->conns = rc;
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn* rc = remote_proxy_find_conn(ctx, stream_id);
if (!rc || rc->sock == SOCKET_INVALID) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "RP handle_data drop: нет соединения для sid=%08x, шлём CLOSE", stream_id);
if (conn) rp_send_msg(inst, conn->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, stream_id, NULL, 0);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
if (rc->error) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (rc->connected) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:SEND fd=%d sid=%08x len=%zu total=%d",
(int)rc->sock, stream_id, data_len, rp_conn_total(rc));
if (data_len > 0) rp_sock_send(rc, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
} else {
size_t new_len = rc->pending_len + data_len;
uint8_t* new_buf = u_realloc(rc->pending_buf, new_len);
if (new_buf) {
if (data_len > 0) memcpy(new_buf + rc->pending_len, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
rc->pending_buf = new_buf; rc->pending_len = new_len;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "RP buffer sid=%08x added=%zu total=%zu", stream_id, data_len, new_len);
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP buffer realloc(%zu) failed sid=%08x", new_len, stream_id);
}
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn** prev = &ctx->conns;
while (*prev) {
struct remote_proxy_conn* rc = *prev;
if (rc->stream_id == stream_id) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d sock_closed=%d connected=%d",
(int)rc->sock, stream_id, rp_conn_total(rc), rc->sock_closed, rc->connected);
rc->cli_closed = 1;
if (rc->sock != SOCKET_INVALID && rc->connected == 1) shutdown(rc->sock, SHUT_WR);
if (rc->sock_closed) rp_conn_free(rc);
return;
}
prev = &rc->next;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "remote_proxy: CLOSE sid=%08x — no conn", stream_id);
}
int remote_proxy_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
memset(ctx, 0, sizeof(*ctx));
if (!inst->config) return 0;
ctx->enabled = inst->config->global.remote_proxy_enabled;
ctx->inst = inst;
if (!ctx->enabled) return 0;
g_rp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_etcp_recv_cb);
if (!inst->config->global.tcp_proxy_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy initialized node=%016llx", (unsigned long long)inst->node_id);
return 0;
}
void remote_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroying: conn_count=%d", ctx->conn_count);
struct remote_proxy_conn* rc = ctx->conns;
while (rc) { struct remote_proxy_conn* next = rc->next; rp_conn_free(rc); rc = next; }
ctx->conns = NULL; ctx->enabled = 0;
if (g_rp_ctx == ctx) g_rp_ctx = NULL;
udp_proxy_destroy(inst);
icmp_proxy_destroy(inst);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroyed");
}
+76
View File
@@ -0,0 +1,76 @@
// remote_proxy.h — Удаленный TCP прокси (exit node)
#ifndef REMOTE_PROXY_H
#define REMOTE_PROXY_H
#include <stdint.h>
#include "../lib/socket_compat.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
struct remote_proxy_ctx;
#define TCP_PROXY_SUBCMD_CONNECT 0x01
#define TCP_PROXY_SUBCMD_CONNECTED 0x02
#define TCP_PROXY_SUBCMD_DATA 0x03
#define TCP_PROXY_SUBCMD_CLOSE 0x04
#define TCP_PROXY_SUBCMD_ERROR 0x05
#define TCP_PROXY_CONNECTED_OK 0
#define TCP_PROXY_CONNECTED_REFUSED 1
#define TCP_PROXY_HDR_SIZE 6 // svc_id(1)+subcmd(1)+stream_id(4)
#define TCP_PROXY_CONNECT_HDR_SIZE 12 // HDR_SIZE + dest_ip(4)+dest_port(2)
#define TCP_PROXY_CONNECTED_HDR_SIZE 9 // HDR_SIZE + local_port(2)+status(1)
struct remote_proxy_conn {
struct remote_proxy_conn* next;
struct remote_proxy_ctx* ctx;
uint32_t stream_id;
uint64_t peer_node_id;
socket_t sock;
void* read_id;
struct UASYNC* ua;
uint8_t dest_ip[4];
uint16_t dest_port;
uint8_t cli_closed; // client sent CLOSE
uint8_t sock_closed; // socket EOF received
uint8_t error;
uint8_t connected; // connect() completed successfully
uint8_t* pending_buf; // буфер данных пока connect() не завершён
size_t pending_len;
uint8_t* out_buf; // буфер отправки в сокет (неблокирующий send)
size_t out_len;
size_t out_off;
void* out_timer; // таймер повтора send
int out_backoff; // backoff: 10..5000 tb (1ms..500ms)
uint8_t* pause_buf; // буфер при паузе чтения (normalizer переполнен)
size_t pause_len;
void* pause_timer; // таймер паузы чтения
int pause_backoff; // backoff: 50..5000 tb (5ms..500ms)
};
struct remote_proxy_ctx {
int enabled;
int conn_count;
struct remote_proxy_conn* conns;
struct UTUN_INSTANCE* inst;
};
int remote_proxy_init(struct UTUN_INSTANCE* inst);
void remote_proxy_destroy(struct UTUN_INSTANCE* inst);
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id);
void rp_conn_free(struct remote_proxy_conn* rc);
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id);
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
#endif
+111
View File
@@ -0,0 +1,111 @@
#include <string.h>
#include <stdint.h>
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/radix.h"
#include "route_node.h"
#include "route6_lib.h"
#define HASH_SIZE 256
#define RADIX_OFF 1 // skip first byte (length) in radix key comparisons
static void make_key6(uint8_t out[ROUTE6_KEY_SIZE], const uint8_t addr[16]) {
out[0] = ROUTE6_KEY_SIZE;
memcpy(out + 1, addr, 16);
}
static void prefix_to_mask6(uint8_t plen, uint8_t mask[16]) {
memset(mask, 0, 16);
if (plen == 0) return;
for (int i = 0; i < 16 && plen > 0; i++) {
if (plen >= 8) { mask[i] = 0xFF; plen -= 8; }
else { mask[i] = (uint8_t)(0xFF00 >> plen); plen = 0; }
}
}
static void make_mask6(uint8_t out[ROUTE6_KEY_SIZE], uint8_t plen) {
out[0] = ROUTE6_KEY_SIZE;
prefix_to_mask6(plen, out + 1);
}
struct ROUTE6_TABLE *route6_table_create(struct UASYNC *ua) {
struct ROUTE6_TABLE *t = u_calloc(1, sizeof(struct ROUTE6_TABLE));
if (!t) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: alloc failed");
return NULL;
}
if (rn_inithead((void**)&t->rnh, RADIX_OFF) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: rn_inithead failed");
u_free(t);
return NULL;
}
t->queue = queue_new(ua, HASH_SIZE, ROUTE6_NODE_ID_OFF, sizeof(uint64_t), "route6");
if (!t->queue) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: queue_new failed");
rn_detachhead((void**)&t->rnh);
u_free(t);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route6_table_create: table=%p", (void*)t);
return t;
}
void route6_table_destroy(struct ROUTE6_TABLE *table) {
if (!table) return;
struct ll_entry *entry;
while ((entry = queue_data_get(table->queue))) {
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
rn_delete(rd->key, rd->mask, &table->rnh->rh);
queue_entry_free(entry);
}
queue_free(table->queue);
rn_detachhead((void**)&table->rnh);
u_free(table);
}
bool route6_insert(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return false;
const struct NODEINFO_IPV6_SUBNET *subnets = NULL;
int cnt = get_node_v6_routes(node, &subnets);
if (cnt <= 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route6_insert: node has no v6 subnets");
return false;
}
for (int i = 0; i < cnt; i++) {
struct ll_entry *entry = queue_entry_new(ROUTE6_DATA_SIZE);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_insert: queue_entry_new failed"); return false; }
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
make_key6(rd->key, subnets[i].addr);
make_mask6(rd->mask, subnets[i].prefix_length);
rd->v_node_info = node;
rd->node_id = node->node.node_id;
rd->prefix_length = subnets[i].prefix_length;
struct radix_node *rn = rn_addroute(rd->key, rd->mask, &table->rnh->rh, rd->rn_nodes);
if (!rn) { queue_entry_free(entry); continue; }
queue_data_put_with_index(table->queue, entry);
}
return true;
}
void route6_delete(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return;
uint64_t node_id = node->node.node_id;
struct ll_entry *entry = queue_find_data_by_index(table->queue, &node_id);
while (entry) {
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
rn_delete(rd->key, rd->mask, &table->rnh->rh);
struct ll_entry *next = queue_find_next_by_index(table->queue, &node_id, entry);
queue_remove_data(table->queue, entry);
queue_entry_free(entry);
entry = next;
}
}
const struct ROUTE6_DATA *route6_lookup(struct ROUTE6_TABLE *table, const uint8_t addr[16]) {
if (!table) return NULL;
uint8_t key[ROUTE6_KEY_SIZE];
make_key6(key, addr);
struct radix_node *leaf = rn_match(key, &table->rnh->rh);
if (!leaf || (leaf->rn_flags & RNF_ROOT)) return NULL;
return (const struct ROUTE6_DATA *)((const char *)leaf - offsetof(struct ROUTE6_DATA, rn_nodes));
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef ROUTE6_LIB_H
#define ROUTE6_LIB_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "../lib/radix.h"
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
// таблица маршрутизации IPv6 на radix tree + ll_queue (поиск по node_id для удаления)
struct ROUTE6_DATA {
struct radix_node rn_nodes[2]; // scratch-буфер для rn_addroute
struct NODEINFO_Q *v_node_info; // узел-владелец
uint64_t node_id; // ключ для hash-поиска в ll_queue
uint8_t prefix_length; // 0..128
uint8_t key[17]; // sockaddr-like: {len=17, addr[16]}
uint8_t mask[17]; // sockaddr-like: {len=17, mask[16]}
};
#define ROUTE6_DATA_SIZE sizeof(struct ROUTE6_DATA)
#define ROUTE6_NODE_ID_OFF offsetof(struct ROUTE6_DATA, node_id)
#define ROUTE6_KEY_SIZE 17
struct ROUTE6_TABLE {
struct radix_node_head *rnh; // radix tree
struct ll_queue *queue; // indexed by node_id for delete
};
struct ROUTE6_TABLE *route6_table_create(struct UASYNC *ua);
void route6_table_destroy(struct ROUTE6_TABLE *table);
bool route6_insert(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node);
void route6_delete(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node);
const struct ROUTE6_DATA *route6_lookup(struct ROUTE6_TABLE *table, const uint8_t addr[16]);
#endif
+695 -186
View File
File diff suppressed because it is too large. Load diff
+83 -5
View File
@@ -16,6 +16,8 @@
#define ROUTE_SUBCMD_NODEINFO 0x04 // полная информация об узле + маршруты
#define ROUTE_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы
#define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
#define ROUTE_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента
#define ROUTE_SUBCMD_NAT_CHECK_REQ 0x0A // запрос от клиента на проверку NAT для сокета
#define MAX_HOPS 16
#define BGP_NODES_HASH_SIZE 256
@@ -52,11 +54,40 @@ struct ROUTE_BGP_CONN_ITEM {
struct ETCP_CONN* conn;
};
/**
* @brief Пакет NAT_INFO (фиксированный)
*/
struct BGP_NAT_INFO {
uint8_t cmd;
uint8_t subcmd;
uint8_t socket_id; // MY socket id (link->conn->sock_id)
uint8_t nat_ip[4]; // network byte order
uint16_t nat_port; // network byte order
uint8_t nat_type; // NAT_TYPE_*
} __attribute__((packed));
/**
* @brief Пакет запроса на проверку NAT от клиента к серверу
*/
struct BGP_NAT_CHECK_REQ {
uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_NAT_CHECK_REQ
uint8_t socket_id; // ID сокета клиента, который надо проверить
uint32_t interface_ip; // interface_addr IP клиента (network byte order)
uint16_t interface_port; // interface_addr port клиента (network byte order)
} __attribute__((packed));
struct route_ping_pending;
struct ETCP_LINK;
struct ROUTE_BGP {
struct UTUN_INSTANCE* instance;
struct ll_queue* senders_list;
struct ll_queue* nodes;
struct NODEINFO_Q* local_node;
struct route_ping_pending* ping_pending;
uint32_t next_ping_req_id;
uint8_t allow_nat_check_local; // 1 = разрешить NAT check для локальных подсетей (для тестов)
};
/**
@@ -115,11 +146,6 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
*/
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
/**
* @brief Рассылает local_node NODEINFO всем senders_list кроме exclude.
*/
void route_bgp_broadcast_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude);
/**
* @brief Отправляет WITHDRAW для node_id (вызывает broadcast_withdraw).
*/
@@ -132,6 +158,17 @@ void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск оптимального ETCP соединения для указанного node_id.
*
* Перебирает paths узла, выбирает путь с минимальным hop_count.
*
* @param bgp указатель на ROUTE_BGP
* @param node_id целевой узел
* @return оптимальный ETCP_CONN* или NULL
*/
struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Добавляет путь (conn) в paths узла.
*
@@ -146,4 +183,45 @@ int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t*
*/
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn);
/**
* @brief Отправляет NAT_INFO клиенту с его типом NAT.
*
* @param conn соединение к клиенту
* @param nat_ip IP клиента (network byte order)
* @param nat_port порт клиента (network byte order)
* @param nat_type NAT_TYPE_EIM или NAT_TYPE_STRICT
*/
void route_bgp_send_nat_info(struct ETCP_CONN* conn, uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type);
/**
* @brief Отправляет запрос на проверку NAT для указанного socket_id серверу.
*
* @param conn соединение к серверу
* @param socket_id ID сокета для проверки
*/
void route_bgp_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id);
/**
* @brief Разрешить/запретить NAT check для локальных подсетей (127.0.0.1, 10.x.x.x и т.д.)
* По умолчанию запрещено. Полезно для тестов.
*/
void route_bgp_set_nat_check_local(struct ROUTE_BGP* bgp, int allow);
/**
* @brief Запускает NAT check для всех линков всех соединений.
*/
void route_bgp_request_nat_check_all(struct ROUTE_BGP* bgp);
/**
* @brief Запускает NAT check для одного линка.
*/
void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link);
/* Аргумент callback NAT check */
struct nat_check_arg {
struct ETCP_LINK* link;
uint32_t nat_ip;
uint16_t nat_port;
};
#endif // ROUTE_BGP_H
+16
View File
@@ -24,3 +24,19 @@ bgp_update local nodelist:
bgp_withdraw(struct ROUTE_BGP bgp, uint64_t node_to_del, uint64_t wd_source) - wd_source это узел который захотел withdraw.
- находим у себя node to del. удаляем если в hoplist найден wd_node (или мы = wd_node)
если удалили - распространяем по всем линкам с этими же аргументами
===============================
определение nat
определяем что оба узла в локальной сети:
-если обе ноды имеют локальные адреса сокетов пробуем пинг по локальным адресам
если узел подключился и его сокетовый ip:port отличается от connection ip:port - устанавливаем флаг nat. и инициируем проверку пингом третьим узлом:
проверка считается успешной если по результату пинга ip источника пинга отличается от ip запросившего пинг узла.
проверка типа nat узла который подключился:
- пингуем узел с целью зафиксировать свой ip (в сокете может быть например 0.0.0.0)
- даём команду третьему узлу пингануть этот узел.
- по результатам двух пингов определяем тип nat
+446
View File
@@ -0,0 +1,446 @@
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "route_node.h"
#include "route_bgp.h"
#include "route_connectivity.h"
#define CONN_MAX_SOCKET_CANDIDATES 8
struct conn_probe_ctx {
struct UTUN_INSTANCE* instance;
struct NODEINFO_Q* nq;
uint8_t addr_type; // ADDR_TYPE_*
struct sockaddr_storage target_addr;
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
struct ETCP_SOCKET* candidate_sockets[CONN_MAX_SOCKET_CANDIDATES];
uint8_t candidate_count;
uint8_t candidate_index;
uint16_t best_across_sockets; // min RTT по всем сокетам
uint8_t count_total; // 3 на серию
uint8_t count_sent;
uint8_t count_ok;
uint16_t min_rtt; // min RTT в текущей серии
uint16_t timeout_ms;
void* ping_timer;
};
// ---- forward ----
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len);
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok);
static void conn_probe_start_series(struct conn_probe_ctx* ctx);
// ---- helpers ----
static int sock_addr_cmp(const struct sockaddr_storage* a, const struct sockaddr_storage* b) {
if (a->ss_family != b->ss_family) return 1;
if (a->ss_family == AF_INET) {
const struct sockaddr_in* sa = (const struct sockaddr_in*)a;
const struct sockaddr_in* sb = (const struct sockaddr_in*)b;
if (sa->sin_addr.s_addr != sb->sin_addr.s_addr) return 1;
if (sa->sin_port != sb->sin_port) return 1;
return 0;
}
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6* sa = (const struct sockaddr_in6*)a;
const struct sockaddr_in6* sb = (const struct sockaddr_in6*)b;
if (memcmp(&sa->sin6_addr, &sb->sin6_addr, 16) != 0) return 1;
if (sa->sin6_port != sb->sin6_port) return 1;
return 0;
}
return memcmp(a, b, sizeof(struct sockaddr_storage));
}
// проверяет что два адреса (NODEINFO_IPV4_ADDR) не дубликаты по IP+port
static int addr_eq(const struct NODEINFO_IPV4_ADDR* a, uint32_t ip, uint16_t port) {
uint32_t a_ip; memcpy(&a_ip, a->addr, 4);
return a_ip == ip && a->port == port;
}
// собирает список локальных сокетов-кандидатов для probing заданного адреса
// сортирует: лучшие (по совпадению подсети / типу NAT) первые
static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
uint8_t addr_type,
const struct sockaddr_storage* target_addr,
struct ETCP_SOCKET** out_sockets, uint8_t max_count) {
if (!instance || !target_addr || !out_sockets || max_count == 0) return 0;
int target_family = target_addr->ss_family;
int found = 0;
struct ETCP_SOCKET* e_sock;
if (target_family == AF_INET6) {
struct sockaddr_in6* target_sin6 = (struct sockaddr_in6*)target_addr;
// IPv6: prefer sockets in same /64 subnet
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family == AF_INET6) {
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
if (memcmp(&if_sin6->sin6_addr, &target_sin6->sin6_addr, 8) == 0) {
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
}
}
e_sock = e_sock->next;
}
// Pass 2: all other IPv6 sockets
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET6) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
e_sock = e_sock->next;
}
return found;
}
// IPv4 target — existing logic
uint32_t target_ip = ((const struct sockaddr_in*)target_addr)->sin_addr.s_addr;
e_sock = instance->etcp_sockets;
// Проход 1: точное совпадение подсети (для INTERFACE) или PUBLIC/NAT_VERIFIED (для NAT/REAL)
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int match = 0;
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
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))
match = 1;
} else {
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC
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;
}
if (match) {
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
}
e_sock = e_sock->next;
}
// Проход 2: все остальные подходящие
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int dup = 0;
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;
} else {
// любой не-private
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
}
}
e_sock = e_sock->next;
}
// Проход 3: остальные IPv4 (fallback)
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
e_sock = e_sock->next;
}
return found;
}
// ---- probe lifecycle ----
static void conn_probe_start_series(struct conn_probe_ctx* ctx) {
if (ctx->candidate_index >= ctx->candidate_count) {
// все сокеты перебраны
if (ctx->best_across_sockets != 65535) conn_probe_finish(ctx, 1);
else conn_probe_finish(ctx, 0);
return;
}
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
ctx->count_sent = 0;
ctx->count_ok = 0;
ctx->min_rtt = 65535;
static const char* atype_names[] = {"INTERFACE", "NAT", "REAL"};
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe series start: socket=%s target=%s:%u type=%s",
sock->name,
ip_to_str(&((struct sockaddr_in*)&ctx->target_addr)->sin_addr, AF_INET).str,
(unsigned)ntohs(((struct sockaddr_in*)&ctx->target_addr)->sin_port),
atype_names[ctx->addr_type]);
int ret = etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
&ctx->target_addr, ctx->timeout_ms,
conn_probe_single_cb, ctx, NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot start ping from socket %s", sock->name);
ctx->count_sent = 3; // simulate full failure
ctx->candidate_index++;
conn_probe_start_series(ctx);
}
}
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len) {
(void)nonce; (void)resp_data; (void)resp_data_len;
struct conn_probe_ctx* ctx = (struct conn_probe_ctx*)arg;
if (!ctx) return;
ctx->count_sent++;
if (success) {
ctx->count_ok++;
if (rtt < ctx->min_rtt) ctx->min_rtt = rtt;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe ping: ok=%d rtt=%u sent=%d/%d socket=%s",
success, rtt, ctx->count_sent, ctx->count_total,
ctx->candidate_sockets[ctx->candidate_index]->name);
if (ctx->count_sent < ctx->count_total) {
// продолжаем с тем же сокетом
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
int ret = etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
&ctx->target_addr, ctx->timeout_ms,
conn_probe_single_cb, ctx, NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot continue ping from socket %s", sock->name);
ctx->count_sent = ctx->count_total; // force finish series
ctx->count_ok = 0;
} else return; // следующий пинг отправлен, ждём callback
}
// серия из 3 пингов завершена
if (ctx->count_ok > 0) {
if (ctx->min_rtt < ctx->best_across_sockets) ctx->best_across_sockets = ctx->min_rtt;
conn_probe_finish(ctx, 1);
} else {
// этот сокет не подошёл — пробуем следующий
ctx->candidate_index++;
conn_probe_start_series(ctx);
}
}
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok) {
if (!ctx || !ctx->nq) return;
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
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);
}
u_free(ctx);
}
// ---- public API ----
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* nq) {
if (!instance || !nq) return;
if (nq->node.node_id == instance->node_id) return; // не пингуем себя
if (nq->connectivity.probe_status == PROBE_STATUS_IN_PROGRESS) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe already in progress for node %016llx", (unsigned long long)nq->node.node_id);
return;
}
nq->connectivity.probe_status = PROBE_STATUS_IN_PROGRESS;
nq->connectivity.probe_start_time = get_time_tb();
nq->connectivity.interface_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.nat_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.real_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.interface_min_rtt = 65535;
nq->connectivity.nat_min_rtt = 65535;
nq->connectivity.real_min_rtt = 65535;
nq->connectivity.pending_count = 0;
int pend = 0;
// ---- IPv4 addresses ----
const struct NODEINFO_IPV4_ADDR* addrs;
int addr_count = get_node_v4_addrs(nq, &addrs);
if (addr_count > 0) {
uint32_t seen_ips[16]; uint16_t seen_ports[16];
int seen_count = 0;
for (int i = 0; i < addr_count; i++) {
uint32_t ip; memcpy(&ip, addrs[i].addr, 4);
uint16_t port = addrs[i].port;
if (ip == 0 || port == 0) continue;
int dup = 0;
for (int j = 0; j < seen_count; j++) {
if (seen_ips[j] == ip && seen_ports[j] == port) { dup = 1; break; }
}
if (dup) continue;
if (seen_count >= 16) break;
seen_ips[seen_count] = ip; seen_ports[seen_count] = port; seen_count++;
struct sockaddr_storage target;
memset(&target, 0, sizeof(target));
struct sockaddr_in* sin = (struct sockaddr_in*)&target;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = ip;
sin->sin_port = htons(port);
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, addrs[i].type, &target,
candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for target %s:%u type=%d",
ip_to_str(&ip, AF_INET).str, port, addrs[i].type);
continue;
}
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
if (!ctx) continue;
ctx->instance = instance;
ctx->nq = nq;
ctx->addr_type = addrs[i].type;
ctx->target_addr = target;
memcpy(ctx->peer_pubkey, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count;
ctx->candidate_index = 0;
ctx->count_total = CONN_PROBE_COUNT;
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
ctx->best_across_sockets = 65535;
pend++;
conn_probe_start_series(ctx);
}
}
// ---- IPv6 addresses ----
const struct NODEINFO_IPV6_ADDR* addrs6;
int addr6_count = get_node_v6_addrs(nq, &addrs6);
if (addr6_count > 0) {
uint8_t seen_ip6s[16][16]; uint16_t seen_ports6[16];
int seen6_count = 0;
for (int i = 0; i < addr6_count; i++) {
if (addrs6[i].port == 0) continue;
// skip zero address
int zero = 1;
for (int z = 0; z < 16; z++) if (addrs6[i].addr[z] != 0) { zero = 0; break; }
if (zero) continue;
int dup = 0;
for (int j = 0; j < seen6_count; j++) {
if (memcmp(seen_ip6s[j], addrs6[i].addr, 16) == 0 && seen_ports6[j] == addrs6[i].port) { dup = 1; break; }
}
if (dup) continue;
if (seen6_count >= 16) break;
memcpy(seen_ip6s[seen6_count], addrs6[i].addr, 16);
seen_ports6[seen6_count] = addrs6[i].port;
seen6_count++;
struct sockaddr_storage target;
memset(&target, 0, sizeof(target));
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&target;
sin6->sin6_family = AF_INET6;
memcpy(&sin6->sin6_addr, addrs6[i].addr, 16);
sin6->sin6_port = htons(addrs6[i].port);
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, addrs6[i].type, &target,
candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for IPv6 target type=%d", addrs6[i].type);
continue;
}
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
if (!ctx) continue;
ctx->instance = instance;
ctx->nq = nq;
ctx->addr_type = addrs6[i].type;
ctx->target_addr = target;
memcpy(ctx->peer_pubkey, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count;
ctx->candidate_index = 0;
ctx->count_total = CONN_PROBE_COUNT;
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
ctx->best_across_sockets = 65535;
pend++;
conn_probe_start_series(ctx);
}
}
if (pend == 0) {
nq->connectivity.probe_status = PROBE_STATUS_DONE;
if (addr_count == 0 && addr6_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "no addresses to probe for node %016llx", (unsigned long long)nq->node.node_id);
}
} else {
nq->connectivity.pending_count = pend;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe started for node %016llx: %d series pending",
(unsigned long long)nq->node.node_id, pend);
}
}
void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* nq) {
if (!instance || !nq) return;
nq->connectivity.probe_status = PROBE_STATUS_NONE;
nq->connectivity.pending_count = 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe cancelled for node %016llx", (unsigned long long)nq->node.node_id);
}
void route_connectivity_cancel_all(struct UTUN_INSTANCE* instance) {
if (!instance || !instance->bgp || !instance->bgp->nodes) return;
int count = 0;
struct ll_entry* e = instance->bgp->nodes->head;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
nq->connectivity.probe_status = PROBE_STATUS_NONE;
nq->connectivity.pending_count = 0;
e = e->next;
count++;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe cancel ALL: %d nodes", count);
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef ROUTE_CONNECTIVITY_H
#define ROUTE_CONNECTIVITY_H
#include <stdint.h>
#include "route_node.h"
struct UTUN_INSTANCE;
// Запускает зондирование связности ко всем адресам удалённого узла
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance,
struct NODEINFO_Q* nq);
// Отменяет все pending пробы для узла (при удалении / withdraw)
void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance,
struct NODEINFO_Q* nq);
// Отменяет все pending пробы для всех узлов (при destroy)
void route_connectivity_cancel_all(struct UTUN_INSTANCE* instance);
#endif // ROUTE_CONNECTIVITY_H
+38 -16
View File
@@ -1,12 +1,13 @@
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "utun_instance.h"
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#define INITIAL_CAPACITY 100
@@ -167,7 +168,8 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
// === Проверка пересечений ===
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4); // addr уже в network byte order (big-endian)
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix = subnets[i].prefix_length;
if (check_route_overlap_in_table(network, prefix,
@@ -195,6 +197,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix_length = subnets[i].prefix_length;
int pos = binary_search_insert_pos(table->entries, table->count, network, prefix_length);
@@ -207,33 +210,33 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
struct ROUTE_ENTRY *e = &table->entries[pos];
e->network = network;
e->prefix_length = prefix_length;
e->v_node_info = node; // все префиксы узла ссылаются на один объект
e->v_node_info = node;
table->count++;
table->stats.learned_routes++;
if (node && node->node.hop_count == 0) table->stats.local_routes++; else table->stats.learned_routes++;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node_info=%p",
count, (void*)node);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node=%016llx",
count, node ? (unsigned long long)node->node.node_id : 0ULL);
return true;
}
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node_info=%p", (void*)node);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node=%016llx",
(unsigned long long)node->node.node_id);
size_t i = 0;
size_t removed = 0;
while (i < table->count) {
if (table->entries[i].v_node_info == node) {
// v_node_info — внешний, не освобождаем
if (i < table->count - 1) {
memmove(&table->entries[i], &table->entries[i + 1],
(table->count - i - 1) * sizeof(struct ROUTE_ENTRY));
}
table->count--;
table->stats.learned_routes--;
if (node && node->node.hop_count == 0) table->stats.local_routes--; else table->stats.learned_routes--;
removed++;
continue;
}
@@ -277,8 +280,7 @@ void route_table_print(const struct ROUTE_TABLE *table) {
const struct ROUTE_ENTRY *entry = &table->entries[i];
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d",
i + 1, ip_to_string(entry->network).a, entry->prefix_length);
if (entry->v_node_info) {
if (entry->v_node_info && entry->v_node_info->node.hop_count != 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " v_node_info=%p", (void*)entry->v_node_info);
} else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " LOCAL");
@@ -306,3 +308,23 @@ int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_leng
return 0;
}
int is_local_subnet(uint32_t ip) {
uint8_t* bip=(uint8_t*)&ip;
uint8_t first_octet = bip[0];
uint8_t second_octet = bip[1];
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "check ip %d.%d : ", first_octet, second_octet);
if (first_octet == 0) return 1;
if (first_octet == 10) return 1;
if (first_octet == 127) return 1;
if (first_octet == 169 && second_octet == 254) return 1;
if (first_octet >= 224 && first_octet <= 239) return 1;
if (first_octet == 172 && second_octet >= 16 && second_octet <= 31) return 1;
if (first_octet == 192 && second_octet == 168) return 1;
if (ip==0xffffffff) return 1;
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "result: global ip %d.%d : ", first_octet, second_octet);
return 0;
}
+10
View File
@@ -110,4 +110,14 @@ void route_table_print(const struct ROUTE_TABLE *table);
*/
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length);
bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
/**
* @brief Проверяет что IP адрес НЕ является публичным (роутится в интернете)
*
* @param ip IP адрес в сетевом порядке (big-endian)
* @return 0 - публичный (роутится в интернете), 1 - локальный/мультикастовый/зарезервированный
*/
int is_local_subnet(uint32_t ip);
#endif // ROUTE_LIB_H
+307 -128
View File
@@ -1,128 +1,307 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части BGP_NODEINFO_Q
* и возвращает прямой указатель на массив struct BGP_NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на BGP_NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) {
return -1;
}
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) {
return 0; // успех, но нет подсетей
}
// Начало динамических полей сразу после фиксированной части NODEINFO
const uint8_t *dynamic = (const uint8_t *)&node->node + sizeof(struct NODEINFO);
// 1. Пропускаем node_name
dynamic += info->node_name_len;
// 2. Пропускаем local_v4_sockets
dynamic += info->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
// 3. Пропускаем local_v6_sockets
dynamic += info->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
// Теперь dynamic указывает точно на начало массива NODEINFO_IPV4_SUBNET
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "get_node_routes: returned %u IPv4 subnets from node %p",
(unsigned)info->local_v4_subnets, (void*)node);
return (int)info->local_v4_subnets;
}
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");
return -1;
}
if (!bgp->local_node) {
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t d = vc * sizeof(struct NODEINFO_IPV4_SUBNET);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + d);
if (!bgp->local_node) return -1;
bgp->local_node->node.node_id = htobe64(instance->node_id);
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = 1;
bgp->local_node->node.local_v4_subnets = vc;
}
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
int changed = (vc != (int)bgp->local_node->node.local_v4_subnets);
if (!changed && vc > 0) {
uint8_t* current = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)current;
s = instance->config->my_subnets;
bool same = true;
while (s) {
if (s->ip.family == AF_INET) {
if (memcmp(ra->addr, &s->ip.addr.v4, 4) != 0 || ra->prefix_length != s->netmask) {
same = false;
break;
}
ra++;
}
s = s->next;
}
changed = !same;
}
if (changed) {
uint8_t oldv = bgp->local_node->node.ver;
bgp->local_node->node.ver = ((oldv + 1) % 255) + 1;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) {
memcpy(ra->addr, &s->ip.addr.v4, 4);
ra->prefix_length = s->netmask;
ra++;
}
s = s->next;
}
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
static const uint8_t* node_dyn_start(const struct NODEINFO* info) {
return (const uint8_t*)info + sizeof(struct NODEINFO);
}
static const uint8_t* skip_name(const uint8_t* p, uint8_t name_len) {
return p + name_len;
}
static const uint8_t* skip_v4_sockets_meta(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_SOCKET_META);
}
static const uint8_t* skip_v4_addrs(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_ADDR);
}
static const uint8_t* skip_v6_sockets_meta(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV6_SOCKET_META);
}
static const uint8_t* skip_v6_addrs(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV6_ADDR);
}
static const uint8_t* skip_v4_subnets(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_SUBNET);
}
int get_node_v4_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SOCKET_META **out_meta) {
if (!node || !out_meta) return -1;
*out_meta = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_sockets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
*out_meta = (const struct NODEINFO_IPV4_SOCKET_META *)dynamic;
return (int)info->local_v4_sockets;
}
int get_node_v4_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_ADDR **out_addrs) {
if (!node || !out_addrs) return -1;
*out_addrs = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_addrs == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
*out_addrs = (const struct NODEINFO_IPV4_ADDR *)dynamic;
return (int)info->local_v4_addrs;
}
int get_node_v6_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SOCKET_META **out_meta) {
if (!node || !out_meta) return -1;
*out_meta = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_sockets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
*out_meta = (const struct NODEINFO_IPV6_SOCKET_META *)dynamic;
return (int)info->local_v6_sockets;
}
int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR **out_addrs) {
if (!node || !out_addrs) return -1;
*out_addrs = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_addrs == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
*out_addrs = (const struct NODEINFO_IPV6_ADDR *)dynamic;
return (int)info->local_v6_addrs;
}
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) return -1;
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
dynamic = skip_v6_addrs(dynamic, info->local_v6_addrs);
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
return (int)info->local_v4_subnets;
}
int get_node_v6_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SUBNET **out_subnets) {
if (!node || !out_subnets) return -1;
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_subnets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
dynamic = skip_v6_addrs(dynamic, info->local_v6_addrs);
dynamic = skip_v4_subnets(dynamic, info->local_v4_subnets);
*out_subnets = (const struct NODEINFO_IPV6_SUBNET *)dynamic;
return (int)info->local_v6_subnets;
}
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");
return -1;
}
size_t name_len = 0;
if (instance->name[0]) {
name_len = strlen(instance->name);
if (name_len > 63) name_len = 63;
}
int vc = 0, vc6 = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) { if (s->ip.family == AF_INET) vc++; else if (s->ip.family == AF_INET6) vc6++; s = s->next; }
int sock_count = 0, addr_count = 0;
int sock6_count = 0, addr6_count = 0;
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET) {
sock_count++;
addr_count++; // INTERFACE
if (e_sock->nat_addr.ss_family == AF_INET) {
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
if (nat_sin->sin_addr.s_addr != 0) {
if (e_sock->nat_type != NAT_VERIFIED_STRICT) addr_count++;
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && 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)
}
e_sock = e_sock->next;
}
size_t dyn = name_len + sock_count * sizeof(struct NODEINFO_IPV4_SOCKET_META)
+ addr_count * sizeof(struct NODEINFO_IPV4_ADDR)
+ sock6_count * sizeof(struct NODEINFO_IPV6_SOCKET_META)
+ addr6_count * sizeof(struct NODEINFO_IPV6_ADDR)
+ vc * sizeof(struct NODEINFO_IPV4_SUBNET)
+ vc6 * sizeof(struct NODEINFO_IPV6_SUBNET);
int changed = 1;
uint8_t old_ver = 0;
if (bgp->local_node) {
old_ver = bgp->local_node->node.ver;
changed = (vc != (int)bgp->local_node->node.local_v4_subnets)
|| (sock_count != (int)bgp->local_node->node.local_v4_sockets)
|| (addr_count != (int)bgp->local_node->node.local_v4_addrs)
|| (name_len != bgp->local_node->node.node_name_len)
|| (memcmp(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) != 0)
|| (vc6 != (int)bgp->local_node->node.local_v6_subnets)
|| (sock6_count != (int)bgp->local_node->node.local_v6_sockets)
|| (addr6_count != (int)bgp->local_node->node.local_v6_addrs);
}
if (changed) {
if (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;
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = (old_ver % 255) + 1;
bgp->local_node->node.local_v4_sockets = sock_count;
bgp->local_node->node.local_v4_addrs = addr_count;
bgp->local_node->node.local_v6_sockets = sock6_count;
bgp->local_node->node.local_v6_addrs = addr6_count;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->node.local_v6_subnets = vc6;
bgp->local_node->node.node_name_len = name_len;
memcpy(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
if (name_len) { memcpy(dp, instance->name, name_len); dp += name_len; }
// Write IPv4 socket meta
struct NODEINFO_IPV4_SOCKET_META* meta = (struct NODEINFO_IPV4_SOCKET_META*)dp;
e_sock = instance->etcp_sockets;
int midx = 0;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET) {
meta[midx].id = e_sock->sock_id;
meta[midx].type = e_sock->nat_type;
midx++;
}
e_sock = e_sock->next;
}
dp += sock_count * sizeof(struct NODEINFO_IPV4_SOCKET_META);
// Write IPv4 addresses
struct NODEINFO_IPV4_ADDR* addrs = (struct NODEINFO_IPV4_ADDR*)dp;
int aidx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
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;
memcpy(addrs[aidx].addr, &if_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(if_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_INTERFACE;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
if (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0) {
if (e_sock->nat_type != NAT_VERIFIED_STRICT) {
memcpy(addrs[aidx].addr, &nat_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(nat_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_NAT;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && 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;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
}
}
e_sock = e_sock->next;
}
dp += addr_count * sizeof(struct NODEINFO_IPV4_ADDR);
// Write IPv6 socket meta
struct NODEINFO_IPV6_SOCKET_META* meta6 = (struct NODEINFO_IPV6_SOCKET_META*)dp;
e_sock = instance->etcp_sockets;
int m6idx = 0;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET6) {
meta6[m6idx].id = e_sock->sock_id;
meta6[m6idx].type = e_sock->nat_type;
m6idx++;
}
e_sock = e_sock->next;
}
dp += sock6_count * sizeof(struct NODEINFO_IPV6_SOCKET_META);
// Write IPv6 addresses (INTERFACE only)
struct NODEINFO_IPV6_ADDR* addrs6 = (struct NODEINFO_IPV6_ADDR*)dp;
int a6idx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
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);
addrs6[a6idx].port = ntohs(if_sin6->sin6_port);
addrs6[a6idx].type = ADDR_TYPE_INTERFACE;
addrs6[a6idx].socket_id = e_sock->sock_id;
a6idx++;
}
e_sock = e_sock->next;
}
dp += addr6_count * sizeof(struct NODEINFO_IPV6_ADDR);
// Write IPv4 subnets
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) { memcpy(ra->addr, &s->ip.addr.v4, 4); ra->prefix_length = s->netmask; ra++; }
s = s->next;
}
dp += vc * sizeof(struct NODEINFO_IPV4_SUBNET);
// Write IPv6 subnets
struct NODEINFO_IPV6_SUBNET* ra6 = (struct NODEINFO_IPV6_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET6) { memcpy(ra6->addr, &s->ip.addr.v6, 16); ra6->prefix_length = s->netmask; ra6++; }
s = s->next;
}
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);
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}
+111 -29
View File
@@ -7,32 +7,102 @@
#include "secure_channel.h"
struct ROUTE_BGP;
struct ETCP_SOCKET;
struct UTUN_INSTANCE;
// ---- типы адресов в плоском списке NODEINFO_IPV4_ADDR ----
#define ADDR_TYPE_INTERFACE 0 // interface_addr сокета (LAN)
#define ADDR_TYPE_NAT 1 // nat_addr после детекции NAT
#define ADDR_TYPE_REAL 2 // подтверждённый прямой интернет-адрес (nat проверка показала совпадение с interface_addr)
// ---- типы NAT (etcp_connections.h) ----
// NAT_TYPE_UNKNOWN(0), NAT_TYPE_EIM(1), NAT_TYPE_STRICT(2)
// NAT_VERIFIED_UNKNOWN(4), NAT_VERIFIED_EIM(5), NAT_VERIFIED_STRICT(6), NAT_VERIFIED_DIRECT(7)
// ---- статусы зондирования связности ----
#define PROBE_STATUS_NONE 0
#define PROBE_STATUS_IN_PROGRESS 1
#define PROBE_STATUS_DONE 2
#define PROBE_RESULT_UNKNOWN 0
#define PROBE_RESULT_REACHABLE 1
#define PROBE_RESULT_UNREACHABLE 2
#define CONN_PROBE_COUNT 3
#define CONN_PROBE_TIMEOUT_MS 1000
// ---- состояние связности с удалённым узлом (локальное, не передаётся по BGP) ----
struct NODE_CONNECTIVITY {
uint8_t probe_status; // PROBE_STATUS_*
uint8_t pending_count; // активных probe series
uint64_t probe_start_time; // timebase
uint16_t interface_min_rtt; // x0.1ms
uint16_t nat_min_rtt;
uint16_t real_min_rtt;
uint8_t interface_status; // PROBE_RESULT_*
uint8_t nat_status;
uint8_t real_status;
uint8_t reserved;
uint64_t interface_probe_time;
uint64_t nat_probe_time;
uint64_t real_probe_time;
};
/**
* @brief Информация о узле
* @brief Информация о узле (передаётся по BGP)
*/
struct NODEINFO {
uint64_t node_id; // (big-endian)
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET число локальных ipv4 сокетов узла (для direct incoming connections)
uint8_t local_v6_sockets; // NODEINFO_IPV6_SOCKET число локальных ipv6 сокетов узла (для direct incoming connections) (пока 0)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET_META число метаданных ipv4 сокетов
uint8_t local_v4_addrs; // NODEINFO_IPV4_ADDR число типизированных ipv4 адресов узла (плоский список)
uint8_t local_v6_sockets; // NODEINFO_IPV6_SOCKET_META число метаданных ipv6 сокетов
uint8_t local_v6_addrs; // NODEINFO_IPV6_ADDR число типизированных ipv6 адресов узла
uint8_t local_v4_subnets; // NODEINFO_IPV4_SUBNET число локальных ipv4 подсетей узла
uint8_t local_v6_subnets; // NODEINFO_IPV6_SUBNET число локальных ipv6 подсетей узла (пока 0)
uint8_t tranzit_nodes; // NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды (минимальный пинг / лучшее качество каналов. выбирается/обновляется узлом)
uint8_t hop_count; // hop list: маршрут по которому распространялся этот NODEINFO_PACKET. для избежания зацикливаний при распространении по узлам. каждый узел при передаче инкрементирует и добавляет в конец свой node_id.
// далее идут динамическип поля по порядку следования полей в этой структуре: char node_name[node_name_len], сокеты, роуты, tranzit nodes, hop list (блоки описаны структурами ниже). hop list - это массив node_id[hop_count].
uint8_t local_v6_subnets; // NODEINFO_IPV6_SUBNET число локальных ipv6 подсетей узла
uint8_t tranzit_nodes; // NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды
uint8_t hop_count; // hop list: маршрут по которому распространялся этот NODEINFO_PACKET
// далее идут динамические поля по порядку:
// char node_name[node_name_len]
// NODEINFO_IPV4_SOCKET_META[local_v4_sockets]
// NODEINFO_IPV4_ADDR[local_v4_addrs]
// NODEINFO_IPV6_SOCKET_META[local_v6_sockets]
// NODEINFO_IPV6_ADDR[local_v6_addrs]
// NODEINFO_IPV4_SUBNET[local_v4_subnets]
// NODEINFO_IPV6_SUBNET[local_v6_subnets]
// NODEINFO_TRANZIT_NODE[tranzit_nodes]
// uint64_t hop_list[hop_count]
} __attribute__((packed));
struct NODEINFO_IPV4_SOCKET {
uint8_t addr[4];// network byte order
uint16_t port;
// метаданные одного сокета
struct NODEINFO_IPV4_SOCKET_META {
uint8_t id; // unique socket id (0-255)
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
} __attribute__((packed));
struct NODEINFO_IPV6_SOCKET {
// одна типизированная адресная запись (плоский список)
struct NODEINFO_IPV4_ADDR {
uint8_t addr[4]; // IP (network byte order)
uint16_t port; // port (host byte order)
uint8_t type; // ADDR_TYPE_INTERFACE/NAT/REAL
uint8_t socket_id; // к какому сокету относится
} __attribute__((packed));
struct NODEINFO_IPV6_SOCKET_META {
uint8_t id; // unique socket id (0-255)
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
} __attribute__((packed));
struct NODEINFO_IPV6_ADDR {
uint8_t addr[16];
uint16_t port;
uint8_t type;
uint8_t socket_id;
} __attribute__((packed));
struct NODEINFO_IPV4_SUBNET {
@@ -55,40 +125,52 @@ struct NODEINFO_PATH {
struct ll_entry ll;
struct ETCP_CONN* conn;
uint8_t hop_count; // hop list: маршрут этого path
} __attribute__((packed));
};
struct NODEINFO_Q {
struct ll_entry ll;
struct ll_queue* paths;
struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH
uint8_t dirty;
uint8_t last_ver;
struct NODE_CONNECTIVITY connectivity; // состояние связности (локальное)
struct ETCP_SOCKET* best_socket;
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
} __attribute__((packed));
};
/**
* @brief Создаёт/обновляет nodeinfo для собственного узла
*
* Собирает данные из локальных структур и упаковывает к структуру (оптимизированную для передачи по сети)
*
* @param instance Указатель на UTUN_INSTANCE (с него сбоираем все данные)
* @param bgp Указатель на ROUTE_BGP (для доступа к my_nodeinfo и instance)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp);
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части NODEINFO_Q
* и возвращает прямой указатель на массив struct NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets);
/**
* @brief Получает указатель на массив IPv6-подсетей узла (без malloc/копирования).
*/
int get_node_v6_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SUBNET **out_subnets);
/**
* @brief Получает указатель на массив метаданных IPv4-сокетов узла.
*/
int get_node_v4_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SOCKET_META **out_meta);
/**
* @brief Получает указатель на плоский список IPv4-адресов узла.
*/
int get_node_v4_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_ADDR **out_addrs);
/**
* @brief Получает указатель на массив метаданных IPv6-сокетов узла.
*/
int get_node_v6_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SOCKET_META **out_meta);
/**
* @brief Получает указатель на плоский список IPv6-адресов узла.
*/
int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR **out_addrs);
#endif // ROUTE_NODE_H
+11
View File
@@ -0,0 +1,11 @@
Установка прямого подключения между узлами
1. узел собирает адреса - кандидаты для подключения:
список пар адрес-порт:
- адреса и порты из конфига сокетов (если не 0.0.0.0)
- адреса и порты из сокетов (nat) где nat = EIM
список ретрансляторов узлов-кандидатов:
- rtt, nodeid, адрес-порт
пары ip:port передаются на peer в команде "direct connection request" ROUTE_SUBCMD_CONN_REQ 0x0B
+388
View File
@@ -0,0 +1,388 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "route_node.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "../lib/u_async.h"
struct route_ping_series_ctx {
struct ETCP_CONN* reply_conn;
uint32_t request_id;
struct sockaddr_storage target_addr;
uint8_t pubkey[SC_PUBKEY_SIZE];
struct ETCP_SOCKET* local_sock;
uint8_t count_total;
uint8_t count_sent;
uint8_t count_ok;
uint32_t sum_rtt; /* в 0.1 ms */
uint16_t timeout_ms;
};
// ==================================== блок обработки запросов на удаленные пинги
struct route_ping_pending {
struct route_ping_pending* next;
struct ROUTE_BGP* bgp;
uint32_t request_id;
route_ping_callback_t callback;
void* arg;
void* timeout_timer;
uint8_t cancelled;
struct ETCP_CONN* via_conn; // через кого отправили запрос (third_conn)
};
// если удаленный узел долго не отвечает - вызываем коллбэк по таймауту
static void route_ping_pending_timeout(void* arg) {
struct route_ping_pending* p = (struct route_ping_pending*)arg;
if (!p) return;
p->timeout_timer = NULL;
if (p->cancelled) {
u_free(p);
return;
}
struct ROUTE_BGP* bgp = p->bgp;
struct route_ping_pending** cur = &bgp->ping_pending;
while (*cur) {
if (*cur == p) {
*cur = p->next;
break;
}
cur = &(*cur)->next;
}
if (p->callback) {
p->callback(0, 0, 0, 0, p->arg);
}
u_free(p);
}
// прошел ответ "серия пигнов на удаленном узле завершена"
void route_ping_handle_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {
if (!bgp || !from_conn || !data || len < sizeof(struct BGP_PING_RESPONSE)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
return;
}
const struct BGP_PING_RESPONSE* resp = (const struct BGP_PING_RESPONSE*)data;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "from=%s request_id=%08x sent=%u ok=%u avg_rtt=%u",
from_conn->log_name, (unsigned)resp->request_id,
(unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt);
struct route_ping_pending** cur = &bgp->ping_pending;
while (*cur) {
struct route_ping_pending* p = *cur;
if (p->request_id == resp->request_id) {
*cur = p->next;
if (p->timeout_timer) {
uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
if (p->callback) {
int success = (resp->count_ok > 0) ? 1 : 0;
p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg);
}
u_free(p);
return;
}
cur = &(*cur)->next;
}
DEBUG_WARN(DEBUG_CATEGORY_BGP, "request_id=%08x not found in pending list",
(unsigned)resp->request_id);
}
void route_ping_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
if (!bgp->ping_pending) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: ping_pending is NULL for %s", conn->log_name);
return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: checking pending for %s", conn->log_name);
int cancelled = 0;
struct route_ping_pending** cur = &bgp->ping_pending;
while (*cur) {
struct route_ping_pending* p = *cur;
struct nat_check_arg* na = (struct nat_check_arg*)p->arg;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, " checking pending req_id=%016llx via=%s link->etcp=%s",
(unsigned)p->request_id,
p->via_conn ? p->via_conn->log_name : "NULL",
(na && na->link && na->link->etcp) ? na->link->etcp->log_name : "NULL");
if ((na && na->link && na->link->etcp == conn) || p->via_conn == conn) {
*cur = p->next;
if (p->timeout_timer) {
uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
p->cancelled = 1;
na->link = NULL;
u_free(na);
u_free(p);
cancelled++;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_conn: cancelled req_id=%016llx",
(unsigned)p->request_id);
} else {
cur = &(*cur)->next;
}
}
if (cancelled == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: no pending found for %s", conn->log_name);
}
}
// ========================================================================
int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
uint32_t target_ip, uint16_t target_port,
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms,
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg,
const uint8_t* pubkey) {
if (!bgp || !to_conn || count == 0 || timeout_ms == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
return -1;
}
size_t pkt_size = pubkey ? sizeof(struct BGP_PING_REQUEST) : offsetof(struct BGP_PING_REQUEST, pubkey);
struct BGP_PING_REQUEST* req_pkt = u_calloc(1, pkt_size);
if (!req_pkt) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed");
return -2;
}
req_pkt->cmd = ETCP_ID_ROUTE_ENTRY;
req_pkt->subcmd = ROUTE_SUBCMD_PING_REQ;
uint32_t new_req_id = bgp->next_ping_req_id++;
req_pkt->request_id = new_req_id;
req_pkt->count = count;
req_pkt->interval_ms = interval_ms;
req_pkt->timeout_ms = timeout_ms;
/* IP уже в network byte order, порт конвертируем */
memcpy(req_pkt->target_ipv4, &target_ip, 4);
req_pkt->target_port = htons(target_port);
if (pubkey) {
memcpy(req_pkt->pubkey, pubkey, SC_PUBKEY_SIZE);
}
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(req_pkt);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "queue_entry_new failed");
return -3;
}
e->dgram = (uint8_t*)req_pkt;
e->len = pkt_size;
int ret = etcp_send(to_conn, e);
if (ret != 0) {
u_free(req_pkt);
queue_entry_free(e);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send failed");
return -4;
}
struct route_ping_pending* pending = u_calloc(1, sizeof(struct route_ping_pending));
if (!pending) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "pending alloc failed");
return -5;
}
pending->bgp = bgp;
pending->request_id = new_req_id; // req_pkt may be freed by etcp_send callback
pending->callback = cb;
pending->arg = arg;
pending->via_conn = to_conn;
pending->next = bgp->ping_pending;
bgp->ping_pending = pending;
pending->timeout_timer = uasync_set_timeout(bgp->instance->ua, wait_timeout_ms * 10, pending, route_ping_pending_timeout, "route_ping");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
(unsigned)pending->request_id,
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
return 0;
}
static void route_ping_series_finish(struct route_ping_series_ctx* ctx) {
if (!ctx || !ctx->reply_conn) {
u_free(ctx);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "zero args");
return;
}
uint16_t avg_rtt = (ctx->count_ok > 0)
? (uint16_t)(ctx->sum_rtt / ctx->count_ok)
: (uint16_t)(ctx->timeout_ms * 10); /* таймаут как "плохо" */
/* Отправляем ответ по BGP */
struct BGP_PING_RESPONSE* resp = u_calloc(1, sizeof(struct BGP_PING_RESPONSE));
if (resp) {
resp->cmd = ETCP_ID_ROUTE_ENTRY;
resp->subcmd = ROUTE_SUBCMD_PING_RESP;
resp->request_id = ctx->request_id;
resp->count_sent = ctx->count_sent;
resp->count_ok = ctx->count_ok;
resp->avg_rtt = avg_rtt;
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = (uint8_t*)resp;
e->len = sizeof(struct BGP_PING_RESPONSE);
etcp_send(ctx->reply_conn, e);
DEBUG_INFO(DEBUG_CATEGORY_BGP,
"PING series done %s request_id=%08x sent=%u ok=%u avg_rtt=%u",
ctx->reply_conn->log_name,
(unsigned)ctx->request_id,
(unsigned)ctx->count_sent,
(unsigned)ctx->count_ok,
(unsigned)avg_rtt);
} else {
u_free(resp);
}
}
u_free(ctx);
}
/* Callback одного пинга из серии */
static void route_ping_single_cb(int success,
uint16_t rtt,
void* arg,
uint64_t nonce,
const uint8_t* resp_data,
size_t resp_data_len) {
(void)nonce; (void)resp_data; (void)resp_data_len;
struct route_ping_series_ctx* ctx = (struct route_ping_series_ctx*)arg;
if (!ctx) return;
/* Обновляем статистику */
ctx->count_sent++;
if (success) {
ctx->count_ok++;
ctx->sum_rtt += rtt;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping cb: ok=%d rtt=%u sent=%d/%d nonce=%016llx",
success, rtt, ctx->count_sent, ctx->count_total, (unsigned long long)nonce);
/* Если ещё не все пакеты отправлены — сразу шлём следующий */
if (ctx->count_sent < ctx->count_total) {
int ret = etcp_send_ping_to_socket(
ctx->reply_conn->instance,
ctx->local_sock,
ctx->pubkey,
&ctx->target_addr,
ctx->timeout_ms,
route_ping_single_cb,
ctx,
NULL, 0);
if (ret != 0) {
/* Не смогли отправить следующий — завершаем серию досрочно */
route_ping_series_finish(ctx);
}
/* else: следующий пинг запущен, ждём его callback */
} else {
/* Все пакеты обработаны */
route_ping_series_finish(ctx);
}
}
// Обработчик PING_REQ (нам запросили "пропингуй узел и верни результат")
void route_ping_handle_req(struct ROUTE_BGP* bgp,
struct ETCP_CONN* from_conn,
const uint8_t* data,
size_t len) {
if (!bgp || !from_conn || !data || len < offsetof(struct BGP_PING_REQUEST, pubkey)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len);
return;
}
const struct BGP_PING_REQUEST* req_pkt = (const struct BGP_PING_REQUEST*)data;
/* Создаём контекст серии */
struct route_ping_series_ctx* ctx = u_calloc(1, sizeof(struct route_ping_series_ctx));
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: alloc ctx failed");
return;
}
ctx->reply_conn = from_conn;
ctx->request_id = req_pkt->request_id;
ctx->count_total = req_pkt->count;
ctx->timeout_ms = req_pkt->timeout_ms;
if (len >= sizeof(struct BGP_PING_REQUEST)) {
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE);
}
/* Целевой адрес - в sockaddr храним в network byte order (как в пакете) */
struct sockaddr_in* sin = (struct sockaddr_in*)&ctx->target_addr;
sin->sin_family = AF_INET;
memcpy(&sin->sin_addr.s_addr, req_pkt->target_ipv4, 4); /* network byte order */
sin->sin_port = req_pkt->target_port; /* порт в network byte order */
/* Если target не указан — можно разрешить из nodeinfo */
/*
if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) {
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id);
if (nq) {
const struct NODEINFO_IPV4_ADDR* addrs;
int count = get_node_v4_addrs(nq, &addrs);
if (count > 0) {
memcpy(&sin->sin_addr.s_addr, addrs[0].addr, 4);
sin->sin_port = addrs[0].port;
}
}
}
*/
/* Ищем первый IPv4-сокет (как в старом коде) */
struct ETCP_SOCKET* ls = bgp->instance->etcp_sockets;
while (ls) {
if (ls->local_addr.ss_family == AF_INET) {
ctx->local_sock = ls;
break;
}
ls = ls->next;
}
if (!ctx->local_sock) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "ping req: no IPv4 socket");
route_ping_series_finish(ctx); /* отправит 0/0 */
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "PING series start request_id=%08x target=%s:%u count=%u timeout=%u",
(unsigned)ctx->request_id, ip_to_str(&sin->sin_addr, AF_INET).str,
ntohs(sin->sin_port), (unsigned)ctx->count_total, (unsigned)ctx->timeout_ms);
/* Запускаем первый пинг (дальше цепочка через callback) */
int ret = etcp_send_ping_to_socket(
bgp->instance,
ctx->local_sock,
ctx->pubkey,
&ctx->target_addr,
ctx->timeout_ms,
route_ping_single_cb,
ctx,
NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: cannot start first ping");
route_ping_series_finish(ctx);
}
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef ROUTE_PING_H
#define ROUTE_PING_H
#include <stdint.h>
#include <stddef.h>
#include "route_bgp.h"
#define ROUTE_SUBCMD_PING_REQ 0x07
#define ROUTE_SUBCMD_PING_RESP 0x08
struct BGP_PING_REQUEST {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_PING_REQ
uint16_t reserved; // padding для выравнивания request_id
uint32_t request_id; // для корреляции
uint8_t count; // число пингов
uint8_t socket_id; // id сокета пингуемого узла
uint16_t interval_ms; // интервал между пингами
uint16_t timeout_ms; // таймаут одного пинга
uint8_t target_ipv4[4]; // custom target IP (0 = use nodeinfo sockets)
uint16_t target_port; // custom target port
uint8_t pubkey[SC_PUBKEY_SIZE]; // pubkey for encryption if target not in nodes
} __attribute__((packed));
struct BGP_PING_RESPONSE {
uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_PING_RESP
uint16_t reserved; // padding для выравнивания
uint32_t request_id;
uint8_t count_sent;
uint8_t count_ok;
uint16_t avg_rtt; // средний RTT в 0.1ms
} __attribute__((packed));
typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg);
// Отправить запрос пинга по произвольному IP:port (используется для NAT-детекции)
// Если target_ip == 0, используется node_id из списка известных узлов
// Если pubkey != NULL, он передается в пакете (для пинга без локального nodeinfo)
int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
uint32_t target_ip, uint16_t target_port,
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms,
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg,
const uint8_t* pubkey);
// Очистить список ожидающих запросов (при уничтожении BGP)
void route_ping_destroy_pending(struct ROUTE_BGP* bgp);
// Отменить pending запросы для указанного соединения (при удалении conn)
void route_ping_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
// Обработчики, вызываемые из route_bgp_receive_cbk
void route_ping_handle_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len);
void route_ping_handle_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len);
#endif // ROUTE_PING_H
+41 -68
View File
@@ -7,6 +7,7 @@
#include "packet_dump.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "pkt_normalizer.h"
#include "utun_instance.h"
#include "../lib/ll_queue.h"
@@ -52,7 +53,7 @@ static uint32_t extract_dst_ip(uint8_t* data, size_t len) {
}
// entry format: <id 1 byte> <ip_packet ...>
static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t src_node_id) {
void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t src_node_id) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "");
if (!instance || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_pkt: invalid arguments: instance=%p entry=%p entry->len=%zu",
@@ -140,79 +141,44 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
return;
}
// Determine destination node ID and connection
uint64_t dst_node_id = instance->node_id; // Default to local node
struct ETCP_CONN* conn = NULL;
if (route->v_node_info == NULL) {
// Local route - send to TUN
struct NODEINFO_Q* nq = route->v_node_info;
if (!nq || nq->node.hop_count == 0) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "Local route to %s", ip_to_str(&addr, AF_INET).str);
} else {
// Learned route - use first available path from NODEINFO_Q
struct NODEINFO_Q* nq = route->v_node_info;
if (nq->paths && nq->paths->head) {
struct ll_entry* path_entry = nq->paths->head;
conn = (struct ETCP_CONN*)path_entry->data;
}
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: no path for node %016llx",
(unsigned long long)nq->node.node_id);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to node %016llx dst=%s",
ip_len, (unsigned long long)nq->node.node_id, ip_to_str(&addr, AF_INET).str);
int send_err = etcp_route_send(instance, nq->node.node_id, entry);
if (send_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_route_send failed: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, send_err);
instance->dropped_packets++;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
dst_node_id = conn->peer_node_id;
if (!conn->normalizer) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: connection for %s has no normalizer", ip_to_str(&addr, AF_INET).str);
instance->dropped_packets++;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
instance->routed_packets++;
return;
}
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TRAFFIC)) {
char* packet_str = dump_ip_packet_to_buffer(ip_data, ip_len);
DEBUG_DEBUG(DEBUG_CATEGORY_TRAFFIC, "DUMP: %s", packet_str);
}
else DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)dst_node_id);
if (route->v_node_info == NULL) {
// Local route - send to TUN (entry has [cmd=0][IP data], TUN skips cmd byte)
int put_err = queue_data_put(instance->tun->input_queue, entry);
if (put_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, put_err);
instance->dropped_packets++;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
// Entry sent to TUN, don't free here
instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
return;
} else {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)src_node_id);
}
// Send to ETCP
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to ETCP %s", ip_len, conn->log_name);
int send_err = etcp_send(conn, entry);
if (send_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_send failed: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, send_err);
int put_err = queue_data_put(instance->tun->input_queue, entry);
if (put_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, put_err);
instance->dropped_packets++;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
// Entry sent successfully, don't free here
instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
}
// Callback for packets from ETCP (via etcp_bind id=0)
// Callback for packets from ETCP (via etcp_router_bind id=0)
static void routing_pkt_from_etcp_cb(struct ETCP_CONN* conn, struct ll_entry* pkt) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "");
if (!conn || !pkt) {
@@ -241,10 +207,10 @@ static void routing_pkt_from_etcp_cb(struct ETCP_CONN* conn, struct ll_entry* pk
// Callback for packets from TUN output queue
static void routing_pkt_from_tun_cb(struct ll_queue* q, void* arg) {
(void)q;
struct UTUN_INSTANCE* instance = (struct UTUN_INSTANCE*)arg;
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_pkt_from_tun_cb: invalid instance arg=%p", arg);
queue_resume_callback(q);
return;
}
@@ -260,7 +226,7 @@ static void routing_pkt_from_tun_cb(struct ll_queue* q, void* arg) {
queue_resume_callback(instance->tun->output_queue);
}
// Initialize routing module for instance
// Initialize routing module for instance (route table only, bind happens via routing_bind)
int routing_create(struct UTUN_INSTANCE* instance) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_create: instance is NULL");
@@ -275,21 +241,28 @@ int routing_create(struct UTUN_INSTANCE* instance) {
return -1;
}
// Bind ID=0 for receiving data packets from ETCP
if (etcp_bind(instance, ETCP_ID_DATA, routing_pkt_from_etcp_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_create: failed to bind ETCP ID=0 for node %016llx",
(unsigned long long)instance->node_id);
route_table_destroy(instance->rt);
instance->rt = NULL;
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module initialized for instance node_id=%016llx",
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table created for instance node_id=%016llx",
(unsigned long long)instance->node_id);
return 0;
}
// Bind DATA handler via etcp_router (must be called after etcp_router_init)
int routing_bind(struct UTUN_INSTANCE* instance) {
if (!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",
(unsigned long long)instance->node_id);
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module bound to etcp_router for node_id=%016llx",
(unsigned long long)instance->node_id);
return 0;
}
// Destroy routing module for instance
void routing_destroy(struct UTUN_INSTANCE* instance) {
if (!instance) return;
@@ -297,8 +270,8 @@ void routing_destroy(struct UTUN_INSTANCE* instance) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Destroying routing module for node_id=%016llx",
(unsigned long long)instance->node_id);
// Unbind ETCP ID=0
etcp_unbind(instance, ETCP_ID_DATA);
// Unbind DATA handler from etcp_router
etcp_router_unbind(instance, ETCP_ID_DATA);
// Clean up route table if exists
if (instance->rt) {
+14 -1
View File
@@ -8,14 +8,22 @@
// Forward declarations
struct ETCP_CONN;
struct UTUN_INSTANCE;
struct ll_entry;
/**
* @brief Initialize routing module for instance
* @brief Initialize routing module for instance (route table only)
* @param instance UTUN instance
* @return 0 on success, -1 on error
*/
int routing_create(struct UTUN_INSTANCE* instance);
/**
* @brief Bind DATA handler via etcp_router (must be called after etcp_router_init)
* @param instance UTUN instance
* @return 0 on success, -1 on error
*/
int routing_bind(struct UTUN_INSTANCE* instance);
/**
* @brief Destroy routing module for instance
* @param instance UTUN instance
@@ -43,4 +51,9 @@ void routing_del_conn(struct ETCP_CONN* etcp);
*/
void routing_set_tun(struct UTUN_INSTANCE* instance);
/**
* @brief Route a single packet (exposed for NAT module interception)
*/
void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t src_node_id);
#endif // ROUTING_H
+19 -8
View File
@@ -130,6 +130,18 @@ static sc_status_t verify_and_strip_crc32(uint8_t *plaintext_with_crc, size_t to
return SC_OK;
}
static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session_key) {
SC_SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE];
sc_sha256_init(&sha_ctx);
sc_sha256_update(&sha_ctx, shared_secret, SC_SHARED_SECRET_SIZE);
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun-v3-session", 15);
sc_sha256_final(&sha_ctx, hash);
memcpy(session_key, hash, SC_SESSION_KEY_SIZE);
}
#ifdef USE_OPENSSL
// OpenSSL-specific implementations
@@ -244,15 +256,15 @@ sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key,
return SC_OK;
}
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key_h, int mode) {
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const uint8_t *peer_public_key_h, int mode) {
uint8_t peer_public_key[SC_PUBKEY_SIZE];
if (mode) {
if (hex_to_binary(peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) {
if (hex_to_binary((const char*)peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid hex key format");
return SC_ERR_INVALID_ARG;
}
} else {
memcpy(peer_public_key, (const uint8_t *)peer_public_key_h, SC_PUBKEY_SIZE);
memcpy(peer_public_key, peer_public_key_h, SC_PUBKEY_SIZE);
}
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid ctx");
@@ -319,7 +331,7 @@ sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_ke
EC_GROUP_free(group);
return SC_ERR_CRYPTO;
}
memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE);
sc_derive_session_key(shared_secret, ctx->session_key);
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE);
ctx->peer_key_set = 1;
ctx->session_ready = 1;
@@ -604,12 +616,12 @@ sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key,
return SC_OK;
}
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key_h, int mode) {
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const uint8_t *peer_public_key_h, int mode) {
uint8_t shared_secret[SC_SHARED_SECRET_SIZE];
uint8_t peer_public_key[SC_PUBKEY_SIZE];
if (mode) {
if (hex_to_binary(peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) {
if (hex_to_binary((const char*)peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid hex key format");
return SC_ERR_INVALID_ARG;
}
@@ -647,8 +659,7 @@ sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_ke
return SC_ERR_CRYPTO;
}
/* Derive session key from shared secret (simple copy for demo) */
memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE);
sc_derive_session_key(shared_secret, ctx->session_key);
/* Store peer public key */
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE);
+2 -2
View File
@@ -12,7 +12,7 @@
#define SC_NONCE_SIZE 13 // CCM requires exactly 13 bytes
#define SC_SHARED_SECRET_SIZE SC_HASH_SIZE
#define SC_SESSION_KEY_SIZE 16
#define SC_TAG_SIZE 8
#define SC_TAG_SIZE 16
#define SC_CRC32_SIZE 4
// Шифрование pubkey при передаче (salt + double SHA256 XOR)
@@ -62,7 +62,7 @@ struct secure_channel {
sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys);
sc_status_t sc_generate_keypair(struct SC_MYKEYS *keys);
sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key);
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key, int mode);// mode: 0-bin 1-hex key format
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const uint8_t *peer_public_key, int mode);// mode: 0-bin 1-hex key format
sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key);
// Криптографические операции
+562
View File
@@ -0,0 +1,562 @@
// tcp_proxy.c — TCP прокси: стек lwIP TCP → ETCP → удалённый exit узел
#include "tcp_proxy.h"
#include "lwip_tcp/lwip_tcp.h"
#include "lwip_tcp/lwip_tcp_priv.h"
#include "lwip_tcp/lwip_tcp_opts.h"
#include "config_parser.h"
#include "tun_if.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "remote_proxy.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <arpa/inet.h>
#endif
#ifndef INADDR_ANY
#define INADDR_ANY 0
#endif
// ====================================================================
// Предварительные объявления
// ====================================================================
static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t proxy_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
static void proxy_err_cb(void *arg, err_t err);
static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb);
static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
static void proxy_feed_from_transport(struct proxy_conn *pc);
static struct ll_entry* entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void proxy_conn_free(struct proxy_conn *pc);
static int proxy_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len);
static int send_data(struct proxy_conn* pc, const uint8_t* data, uint16_t len);
// ====================================================================
// Помощник ll_entry
// ====================================================================
static struct ll_entry* entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
struct ll_entry* e = queue_entry_new_from_pool(pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "entry_from_data: pool exhausted"); return NULL; }
e->len = 0; e->dgram = NULL;
if (len > 0) {
uint8_t* buf = u_malloc(len);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
memcpy(buf, data, len); e->dgram = buf; e->len = len;
}
return e;
}
// ====================================================================
// Протокол: сборка и отправка сообщений прокси через ETCP
// ====================================================================
static int proxy_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "proxy_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, "proxy_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[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, dst, e);
}
static int send_connect(struct proxy_conn* pc) {
uint8_t buf[6];
memcpy(buf, pc->dest_ip, 4); memcpy(buf + 4, &pc->dest_port, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3],
ntohs(pc->dest_port), (unsigned long long)pc->proxy->via_node_id);
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6);
}
static int send_data(struct proxy_conn* pc, const uint8_t* data, uint16_t len) {
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len);
}
static int send_close(struct proxy_conn* pc) {
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0);
}
static int send_error(struct proxy_conn* pc) {
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0);
}
// ====================================================================
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
// ====================================================================
static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
struct tcp_proxy *proxy = (struct tcp_proxy *)arg;
(void)src_ip; (void)dst_ip;
uint16_t len = p->tot_len;
if (len > 2000) return LERR_BUF;
uint8_t buf[2002];
if (proxy->tun) {
pbuf_copy_partial(p, buf, len, 0);
ssize_t wr = tun_platform_write(proxy->tun, buf, len);
if (wr != (ssize_t)len) {
uint16_t ip_total = ((uint16_t)buf[2] << 8) | buf[3];
uint32_t sip, dip; memcpy(&sip, buf + 12, 4); memcpy(&dip, buf + 16, 4);
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TUN_WRITE_ERR ret=%zd len=%u ip_total=%u", wr, len, ip_total);
}
}
return LERR_OK;
}
// ====================================================================
// Обработчик не-TCP (UDP/ICMP прокси)
// ====================================================================
static int tcp_proxy_handle_non_tcp(struct tcp_proxy* p, uint8_t* buf, size_t len) {
if (len < 20) return 0;
uint8_t ip_ver = (buf[0] >> 4) & 0xF;
if (ip_ver != 4) return 0;
uint8_t proto = buf[9];
if (proto == IPPROTO_UDP) {
if (len < 28) return 0;
uint32_t src_ip, dst_ip; uint16_t src_port, dst_port;
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
memcpy(&src_port, buf + 20, 2); memcpy(&dst_port, buf + 22, 2);
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, buf + 28, len - 28);
return 1;
}
if (proto == IPPROTO_ICMP) {
if (len < 28) return 0;
uint8_t icmp_type = buf[20];
if (icmp_type != 8) return 0;
uint32_t src_ip, dst_ip;
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
uint16_t icmp_id, icmp_seq;
memcpy(&icmp_id, buf + 24, 2); memcpy(&icmp_seq, buf + 26, 2);
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, src_ip, icmp_id, icmp_seq, buf + 28, len - 28);
return 1;
}
return 0;
}
// ====================================================================
// Помощник: передача данных из очереди to_lwip в lwIP TCP
// ====================================================================
static void proxy_feed_from_transport(struct proxy_conn *pc) {
if (!pc->to_lwip || !pc->pcb) return;
int sent_any = 0;
uint32_t q_pre = queue_entry_count(pc->to_lwip);
while (1) {
uint16_t space = tcp_sndbuf(pc->pcb);
if (space < TCP_MSS / 2) break;
struct ll_entry *e = queue_data_get(pc->to_lwip);
if (!e) break;
if (e->len <= space) {
err_t ret = tcp_write(pc->pcb, e->dgram, e->len, TCP_WRITE_FLAG_COPY);
if (ret == LERR_OK) { sent_any = 1; queue_dgram_free(e); queue_entry_free(e); }
else { queue_data_put_first(pc->to_lwip, e); break; }
} else {
queue_data_put_first(pc->to_lwip, e);
break;
}
queue_resume_callback(pc->to_lwip);
}
queue_resume_callback(pc->to_lwip);
if (sent_any) {
uint32_t unsent = 0; struct tcp_seg* s;
for (s = pc->pcb->unsent; s; s = s->next) unsent++;
DEBUG_DEBUG(DEBUG_CATEGORY_TRAFFIC, "PROXY FEED exit->client sid=%08x fed=%u q=%u->%u snd_wnd=%u cwnd=%u unsent=%u",
pc->stream_id, sent_any, q_pre, queue_entry_count(pc->to_lwip), pc->pcb->snd_wnd, pc->pcb->cwnd, unsent);
tcp_output(pc->pcb);
}
}
// ====================================================================
// lwIP TCP коллбэки
// ====================================================================
static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
struct tcp_proxy *p = (struct tcp_proxy *)arg;
if (err != LERR_OK || !newpcb) return LERR_ABRT;
struct proxy_conn *pc = u_calloc(1, sizeof(struct proxy_conn));
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: accept alloc failed"); return LERR_MEM; }
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
int i;
for (i = 0; i < p->mapping_count; i++) {
if (p->mappings[i].local_port == newpcb->local_port) {
struct in_addr ra; ra.s_addr = inet_addr(p->mappings[i].remote_ip);
memcpy(pc->dest_ip, &ra.s_addr, 4);
pc->dest_port = htons(p->mappings[i].remote_port);
break;
}
}
if (i == p->mapping_count) {
memcpy(pc->dest_ip, &newpcb->local_ip, 4);
pc->dest_port = htons(newpcb->local_port);
}
tcp_arg(newpcb, pc);
tcp_recv(newpcb, proxy_recv_cb);
tcp_sent(newpcb, proxy_sent_cb);
tcp_err(newpcb, proxy_err_cb);
tcp_poll(newpcb, proxy_poll_cb, 2);
tcp_nagle_disable(newpcb);
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
if (send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); u_free(pc); return LERR_MEM; }
pc->next = p->conns; p->conns = pc; p->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
pc->stream_id, newpcb->local_port, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), p->conn_count, newpcb->snd_wnd, newpcb->mss);
return LERR_OK;
}
static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
if (!pc) { if (p) pbuf_free(p); return LERR_OK; }
if (p == NULL || err != LERR_OK) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN sid=%08x pcb_state=%u sndbuf=%u cwnd=%u unsent=%p unacked=%p close_sent=%d",
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
(void*)pcb->unsent, (void*)pcb->unacked, pc->close_sent);
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
pc->tun_closed = 1;
if (!pc->close_sent) {
if (send_close(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY FIN send_close failed sid=%08x", pc->stream_id);
pc->close_sent = 1;
}
return LERR_OK;
}
uint16_t len = p->tot_len;
if (pc->error || pc->rem_closed) {
tcp_recved(pcb, len); pbuf_free(p); return LERR_OK;
}
uint8_t *data = u_malloc(len);
if (data) {
pbuf_copy_partial(p, data, len, 0);
if (send_data(pc, data, len) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv send_data failed sid=%08x len=%u", pc->stream_id, len);
u_free(data);
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
tcp_recved(pcb, len); pbuf_free(p);
return LERR_OK;
}
static err_t proxy_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
(void)len;
struct proxy_conn *pc = (struct proxy_conn *)arg;
if (!pc) return LERR_OK;
proxy_feed_from_transport(pc);
return LERR_OK;
}
static void proxy_err_cb(void *arg, err_t err) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY proxy_err_cb: pc=NULL err=%d", err); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: error %d sid=%08x pcb_state=%u tun_closed=%d rem_closed=%d connected=%d",
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->tun_closed, pc->rem_closed, pc->connected);
pc->error = 1;
if (send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
pc->close_sent = 1;
}
static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
if (!pc) return LERR_OK;
if (pc->error) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x tun_closed=%d rem_closed=%d connected=%d", pc->stream_id, pc->tun_closed, pc->rem_closed, pc->connected);
if (pc->pcb) { tcp_arg(pc->pcb, NULL); tcp_abort(pc->pcb); pc->pcb = NULL; }
proxy_conn_free(pc);
return LERR_OK;
}
if (pc->tun_closed && pc->rem_closed && pc->pcb) {
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
int done = (pending == 0 && pcb->unsent == NULL && pcb->unacked == NULL);
uint32_t sndbuf = tcp_sndbuf(pcb);
if (done || sndbuf == 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP sid=%08x state=%u sndbuf=%u cwnd=%u pending=%u unsent=%p unacked=%p",
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
pending, (void*)pcb->unsent, (void*)pcb->unacked);
struct tcp_pcb *save = pc->pcb; pc->pcb = NULL;
tcp_arg(save, NULL);
{ uint16_t wnd_gap = TCP_WND_MAX(save) - save->rcv_wnd;
if (wnd_gap > 0) tcp_recved(save, wnd_gap); }
while (save->unsent) { struct tcp_seg *seg = save->unsent; save->unsent = seg->next; u_free(seg); }
tcp_close(save);
proxy_conn_free(pc);
}
}
return LERR_OK;
}
// ====================================================================
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
// ====================================================================
static void tcp_proxy_ensure_outbound_listen(struct tcp_proxy* p, uint16_t dport_net) {
uint16_t dport_host = ntohs(dport_net);
struct tcp_pcb* lp = p->lwip->listen_pcbs;
while (lp) { if (lp->local_port == dport_host) return; lp = lp->next; }
struct tcp_pcb* lpcb = tcp_new(p->lwip);
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: tcp_new failed for dynamic listen port %u", dport_host); return; }
tcp_bind(lpcb, INADDR_ANY, dport_net);
struct tcp_pcb* listen_pcb = tcp_listen(lpcb);
if (listen_pcb) {
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, proxy_accept_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: dynamic listen on port %u", dport_host);
}
}
// ====================================================================
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
// ====================================================================
static void tcp_proxy_tun_input(struct ll_queue* q, void* arg) {
struct tcp_proxy* p = (struct tcp_proxy*)arg;
struct ll_entry* entry = queue_data_get(q);
if (!entry) return;
if (entry->dgram && entry->len > 1) {
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
if (len > 20 && len <= 2000) {
if (!tcp_proxy_handle_non_tcp(p, ip, len)) {
uint8_t proto = ip[9];
if (proto == IPPROTO_TCP) {
uint16_t ip_hdr_len = (ip[0] & 0x0F) * 4;
uint16_t ip_total = ((uint16_t)ip[2] << 8) | ip[3];
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len && len >= ip_total) {
uint16_t dport_net; memcpy(&dport_net, ip + ip_hdr_len + 2, 2);
tcp_proxy_ensure_outbound_listen(p, dport_net);
uint32_t src_ip, dst_ip;
memcpy(&src_ip, ip + 12, 4); memcpy(&dst_ip, ip + 16, 4);
uint16_t tcp_len = ip_total - ip_hdr_len;
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
if (pb) { pbuf_take(pb, ip + ip_hdr_len, tcp_len); lwip_tcp_input(p->lwip, pb, src_ip, dst_ip); }
}
}
}
}
}
queue_dgram_free(entry); queue_entry_free(entry); queue_resume_callback(q);
}
// ====================================================================
// Очистка proxy_conn
// ====================================================================
static void proxy_conn_free(struct proxy_conn *pc) {
if (!pc) return;
struct tcp_proxy *p = pc->proxy;
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FREE sid=%08x total_conns=%d to_lwip_q=%u",
pc->stream_id, p->conn_count, pending);
struct proxy_conn **prev = &p->conns;
while (*prev) { if (*prev == pc) { *prev = pc->next; p->conn_count--; break; } prev = &(*prev)->next; }
if (pc->to_lwip) {
struct ll_entry *e;
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
queue_free(pc->to_lwip); pc->to_lwip = NULL;
}
u_free(pc);
}
// ====================================================================
// Обработчики входящих сообщений (сторона клиента)
// ====================================================================
static struct proxy_conn* find_pc_by_stream(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc;
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
return NULL;
}
static void handle_connected(struct tcp_proxy* p, uint32_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CONNECTED sid=%08x — no conn, drop", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) {
uint8_t status = entry->dgram[TCP_PROXY_HDR_SIZE + 2];
if (status == TCP_PROXY_CONNECTED_OK) {
pc->connected = 1;
uint16_t local_port = 0; memcpy(&local_port, entry->dgram + TCP_PROXY_HDR_SIZE, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CONNECTED sid=%08x exit_port=%u", stream_id, ntohs(local_port));
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: remote refused sid=%08x", stream_id);
pc->rem_closed = 1;
}
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void handle_data(struct tcp_proxy* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (!pc) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — нет соединения, шлём CLOSE", stream_id);
if (conn) proxy_send_msg(p->inst, conn->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, stream_id, NULL, 0);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
if (pc->rem_closed || pc->error) { queue_dgram_free(entry); queue_entry_free(entry); return; }
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
if (data_len > 0) {
struct ll_entry* e = entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
if (e) queue_data_put(pc->to_lwip, e);
proxy_feed_from_transport(pc);
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void handle_close(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (pc) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x tun_closed=%d connected=%d",
stream_id, pc->tun_closed, pc->connected);
pc->rem_closed = 1;
} else DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn", stream_id);
}
static void handle_error(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (pc) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x tun_closed=%d rem_closed=%d connected=%d",
stream_id, pc->tun_closed, pc->rem_closed, pc->connected);
pc->error = 1;
} else DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn", stream_id);
}
// ====================================================================
// Единый обработчик etcp_router (диспетчеризация в tcp_proxy или remote_proxy)
// ====================================================================
void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: bad entry len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
struct tcp_proxy* proxy = inst ? inst->tcp_proxy : NULL;
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
remote_proxy_handle_connect(inst, entry, stream_id, src_node_id);
return;
}
if (inst && inst->remote_proxy.enabled) {
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
if (rc) {
if (subcmd == TCP_PROXY_SUBCMD_DATA) { remote_proxy_handle_data(inst, conn, entry, stream_id); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { remote_proxy_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP ERROR recv sid=%08x", stream_id); rc->error = 1; rp_conn_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
}
if (proxy) {
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) { handle_connected(proxy, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_DATA) { handle_data(proxy, conn, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Публичное API
// ====================================================================
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_mapping_config* mappings, int mapping_count,
uint64_t via_node_id)
{
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: ua is NULL"); return NULL; }
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: tun name/ip required"); return NULL; }
struct tcp_proxy* p = u_calloc(1, sizeof(struct tcp_proxy));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_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->entry_pool = memory_pool_init(sizeof(struct ll_entry));
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_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: 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_tun_input, p);
p->lwip = lwip_tcp_init(ua, tcp_output_cb, p);
if (!p->lwip) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_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, proxy_accept_cb);
}
}
}
if (inst) {
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_etcp_recv_cb) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: etcp_router_bind failed");
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY);
udp_proxy_init(inst, ua);
icmp_proxy_init(inst, ua);
}
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
return p;
}
void tcp_proxy_destroy(struct tcp_proxy* p) {
if (!p) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy destroying: conns=%d", p->conn_count);
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY);
udp_proxy_destroy(p->inst);
icmp_proxy_destroy(p->inst);
if (p->lwip) { lwip_tcp_destroy(p->lwip); p->lwip = NULL; }
struct proxy_conn* pc = p->conns;
while (pc) {
struct proxy_conn* next = pc->next;
if (pc->pcb) { tcp_arg(pc->pcb, NULL); tcp_abort(pc->pcb); pc->pcb = NULL; }
if (pc->to_lwip) {
struct ll_entry *e;
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
queue_free(pc->to_lwip); pc->to_lwip = NULL;
}
u_free(pc); pc = next;
}
p->conns = NULL;
if (p->tun) tun_close(p->tun);
if (p->entry_pool) memory_pool_destroy(p->entry_pool);
u_free(p);
}
+63
View File
@@ -0,0 +1,63 @@
// tcp_proxy.h — TCP прокси: стек lwIP TCP → ETCP → удалённый exit узел
#ifndef TCP_PROXY_H
#define TCP_PROXY_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/socket_compat.h"
struct UASYNC;
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct tun_if;
struct ll_queue;
struct ll_entry;
struct memory_pool;
struct tcp_proxy_mapping_config;
struct remote_proxy_ctx;
struct lwip_tcp_ctx;
struct tcp_pcb;
struct proxy_conn {
struct proxy_conn* next;
struct tcp_proxy* proxy;
struct tcp_pcb* pcb;
uint32_t stream_id;
struct ll_queue* to_lwip; // DATA от exit → lwIP (управление потоком)
uint8_t tun_closed; // сторона lwIP отправила FIN
uint8_t rem_closed; // exit отправил CLOSE
uint8_t error; // ошибка, немедленная очистка
uint8_t close_sent; // отправили CLOSE/ERROR в exit
uint8_t connected; // CONNECTED(OK) получен от exit
uint8_t dest_ip[4];
uint16_t dest_port;
};
struct tcp_proxy {
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
struct tun_if* tun;
struct proxy_conn* conns;
int conn_count;
struct memory_pool* entry_pool;
uint32_t next_stream_id;
uint64_t via_node_id;
struct lwip_tcp_ctx* lwip;
struct tcp_proxy_mapping_config* mappings; // указатель на конфиг
int mapping_count;
};
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_mapping_config* mappings, int mapping_count,
uint64_t via_node_id);
void tcp_proxy_destroy(struct tcp_proxy* p);
void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
#endif // TCP_PROXY_H
+5 -4
View File
@@ -154,8 +154,8 @@ static int tun_set_ip(const char *ifname, const char *ip_addr) {
}
// Формируем команду ifconfig с destination address (same as local for /32)
char cmd[512];
snprintf(cmd, sizeof(cmd), "ifconfig %s inet %s %s netmask %s up",
ifname, ip_str, ip_str, mask_str);
snprintf(cmd, sizeof(cmd), "ifconfig %s inet %s 192.0.2.1 netmask %s up",
ifname, ip_str, mask_str);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Executing: %s", cmd);
int ret = system(cmd);
if (ret != 0) {
@@ -189,7 +189,7 @@ static int tun_set_ip(const char *ifname, const char *ip_addr) {
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
struct sockaddr_in *sin_dst = (struct sockaddr_in *)&ifr.ifr_dstaddr;
sin_dst->sin_family = AF_INET;
sin_dst->sin_addr = sin->sin_addr; // same as local
inet_pton(AF_INET, "192.0.2.1", &sin_dst->sin_addr);
if (ioctl(sock, SIOCSIFDSTADDR, &ifr) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "SIOCSIFDSTADDR failed: %s", strerror(errno));
close(sock);
@@ -356,7 +356,8 @@ ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
iv[0].iov_len = sizeof(family);
iv[1].iov_base = (void*)buf;
iv[1].iov_len = len;
return writev(tun->fd, iv, 2);
ssize_t wr = writev(tun->fd, iv, 2);
return wr > 0 ? wr - 4 : wr; // вычесть 4-байтовый AF_INET заголовок
}
// Get poll fd for uasync
int tun_platform_get_poll_fd(struct tun_if* tun) {
+98 -18
View File
@@ -10,6 +10,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include "../lib/mem.h"
#ifdef _WIN32
@@ -126,6 +128,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
tun->ua = ua;
tun->test_mode = test_mode;
tun->fd = -1;
tun->test_fd = -1;
const char* name = config->global.tun_ifname;
if (name && name[0]) strncpy(tun->ifname, name, sizeof(tun->ifname)-1);
@@ -145,49 +148,58 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
u_free(tun);
return NULL;
}
} else if (tun->ifname[0] == '\0') {
strncpy(tun->ifname, "tun_test", sizeof(tun->ifname)-1);
} else {
if (tun->ifname[0] == '\0') strncpy(tun->ifname, "tun_test", sizeof(tun->ifname)-1);
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));
u_free(tun);
return NULL;
}
tun->fd = fds[0];
tun->test_fd = fds[1];
}
tun->pool = memory_pool_init(sizeof(struct ll_entry));
if (!tun->pool) goto fail;
tun->output_queue = queue_new(ua, 0, "TUN output");
tun->input_queue = queue_new(ua, 0, "TUN input");
tun->output_queue = queue_new(ua, 0, 0, 0, "TUN output");
tun->input_queue = queue_new(ua, 0, 0, 0, "TUN input");
if (!tun->output_queue || !tun->input_queue) goto fail;
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
if (!test_mode) {
int poll_fd = tun_platform_get_poll_fd(tun);
if (poll_fd >= 0) {
#ifndef _WIN32
// ==================== LINUX ====================
{
int poll_fd = test_mode ? tun->fd : tun_platform_get_poll_fd(tun);
if (poll_fd >= 0) {
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
if (!tun->socket_id) goto fail;
}
}
#endif
} else {
#ifdef _WIN32
// ==================== WINDOWS ====================
if (!test_mode) {
int poll_fd = tun_platform_get_poll_fd(tun);
if (poll_fd < 0) {
tun->running = 1;
tun->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!tun->stop_event) goto fail;
tun->read_thread = CreateThread(NULL, 0, tun_read_thread_proc, tun, 0, NULL);
if (!tun->read_thread) {
CloseHandle(tun->stop_event);
goto fail;
}
#endif
}
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s initialized (%s mode)", tun->ifname,
test_mode ? "TEST" : "REAL");
return tun;
fail:
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
#ifdef _WIN32
if (tun->stop_event) CloseHandle(tun->stop_event);
if (tun->read_thread) {
@@ -204,6 +216,75 @@ fail:
return NULL;
}
// ===================================================================
// Init NAT TUN (separate TUN interface for NAT traffic)
// ===================================================================
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;
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
if (!tun) return NULL;
tun->ua = ua;
tun->test_mode = test_mode;
tun->fd = -1;
tun->test_fd = -1;
strncpy(tun->ifname, ifname, sizeof(tun->ifname) - 1);
char ip_str[64];
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) {
u_free(tun);
return NULL;
}
} else {
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));
u_free(tun);
return NULL;
}
tun->fd = fds[0];
tun->test_fd = fds[1];
}
tun->pool = memory_pool_init(sizeof(struct ll_entry));
if (!tun->pool) goto fail2;
tun->output_queue = queue_new(ua, 0, 0, 0, "NAT TUN output");
tun->input_queue = queue_new(ua, 0, 0, 0, "NAT TUN input");
if (!tun->output_queue || !tun->input_queue) goto fail2;
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
#ifndef _WIN32
{
int poll_fd = test_mode ? tun->fd : tun_platform_get_poll_fd(tun);
if (poll_fd >= 0) {
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
if (!tun->socket_id) goto fail2;
}
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "NAT TUN %s initialized (%s mode)", tun->ifname,
test_mode ? "TEST" : "REAL");
return tun;
fail2:
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
if (tun->output_queue) queue_free(tun->output_queue);
if (tun->input_queue) queue_free(tun->input_queue);
if (tun->pool) memory_pool_destroy(tun->pool);
if (!test_mode) tun_platform_cleanup(tun);
u_free(tun);
return NULL;
}
// ===================================================================
// Остальные функции (без изменений)
// ===================================================================
@@ -225,6 +306,8 @@ void tun_close(struct tun_if* tun)
uasync_remove_socket(tun->ua, tun->socket_id);
}
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
struct ll_entry* pkt;
while ((pkt = queue_data_get(tun->input_queue)) != NULL) {
queue_dgram_free(pkt);
@@ -248,11 +331,9 @@ ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len)
if (!tun || !buf || len <= 1) return -1;
if (tun->test_mode) {
// Skip prefix byte for test mode injection
return tun_inject_packet(tun, buf + 1, len - 1) == 0 ? (ssize_t)(len - 1) : -1;
}
// Skip prefix byte, write from second byte
ssize_t n = tun_platform_write(tun, buf + 1, len - 1);
if (n > 0) {
tun->bytes_written += n;
@@ -269,6 +350,7 @@ ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len)
struct ll_queue* tun_get_input_queue(struct tun_if* tun) { return tun ? tun->input_queue : NULL; }
struct ll_queue* tun_get_output_queue(struct tun_if* tun) { return tun ? tun->output_queue : NULL; }
int tun_is_test_mode(struct tun_if* tun) { return tun ? tun->test_mode : -1; }
int tun_get_test_fd(struct tun_if* tun) { return tun ? tun->test_fd : -1; }
// ===================================================================
// Callback для обработки пакетов из TUN в main thread
@@ -306,10 +388,9 @@ int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len)
{
if (!tun || !buf || len == 0 || len > TUN_MAX_PACKET_SIZE) return -1;
// Allocate with +1 for prefix byte
uint8_t* data = u_malloc(len + 1);
if (!data) { tun->read_errors++; return -1; }
data[0] = 0; // Prefix byte
data[0] = 0;
memcpy(data + 1, buf, len);
struct ll_entry* pkt = queue_entry_new_from_pool(tun->pool);
@@ -344,7 +425,6 @@ ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len)
return -1;
}
// Add prefix byte at start
buf[0] = 0;
memcpy(buf + 1, pkt->dgram, pkt->len);
ssize_t ret = pkt->len + 1;
+18
View File
@@ -49,6 +49,7 @@ struct tun_if {
struct ll_queue* input_queue; // routing → TUN (пакеты в интерфейс)
int test_mode; // 1 = тестовый режим (без реального TUN)
int test_fd; // в тестовом режиме: вторая сторона socket pair для тестов, -1 иначе
// Статистика
uint64_t bytes_read;
@@ -75,6 +76,17 @@ struct tun_if {
*/
struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config);
/**
* @brief Инициализация TUN интерфейса с явными параметрами (для NAT TUN)
* @param ua экземпляр uasync
* @param ifname имя интерфейса
* @param ip_addr IP-адрес (строка, например "10.0.1.1")
* @param mtu MTU интерфейса
* @param test_mode тестовый режим
* @return указатель на tun_if или NULL при ошибке
*/
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr, int mtu, int test_mode);
/**
* @brief Закрытие и освобождение всех ресурсов
*/
@@ -100,6 +112,12 @@ struct ll_queue* tun_get_output_queue(struct tun_if* tun);
*/
int tun_is_test_mode(struct tun_if* tun);
/**
* @brief Получить test_fd (вторая сторона socket pair) для тестов
* @return fd или -1 если не тестовый режим
*/
int tun_get_test_fd(struct tun_if* tun);
/**
* @brief Инъекция пакета в output_queue (для тестов и Windows-уведомлений)
*/
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff. Show more