Author SHA1 Message Date
Evgeny 9a3eb1ce80 fix: reconnect test — clear_queue callback deadlock + conn_ok timing
- etcp_conn_reset: queue_resume_callback after clear_queue (input_queue,
  input_send_q, input_wait_ack) to prevent suspended callback deadlock
- is_connection_established: check conn->initialized (reliable across
  reinit) instead of link->initialized (never cleared on reinit)
- test: force etcp_conn_reinit after server recreation so conn_ok
  waits for fresh INIT handshake
- test: guard send_packets() with is_connection_established in reconnect
  phases to avoid sending data while conn is down
2026-06-09 19:35:06 +03:00
Evgeny 5982b011b4 etcp: restore etcp_link_update_inflight_lim + state machine fixes + diagnostics
- etcp_link_update_inflight_lim: self-contained method with clamping,
  send_blocked_inflight check, loadbalancer_link_ready on cwnd increase
- etcp_conn_on_inflight_lim_changed: recalc optimal_inflight + resume input
- etcp_ack_recv: use etcp_link_update_inflight_lim instead of manual set
- etcp_request_pkt: fix line 928 check old link (inf_pkt->last_link) not new
- etcp_link_close: recalc optimal_inflight on link removal (both branches)
- etcp_socket_remove: fix dangling pointer loop (nullify after close)
- uasync_print_resources: show timer names, remain time, deleted entries
- diagnostics in utun_instance_destroy and test_etcp_reconnect
2026-06-09 16:40:37 +03:00
Evgeny e55c4ec7ce bugfixes 2026-06-09 13:18:42 +03:00
Evgeny defb70f63a 1 2026-06-06 00:22:21 +03:00
Evgeny 40c7a5b487 1 2026-06-05 22:34:49 +03:00
Evgeny e81299a727 1 2026-06-05 21:43:26 +03:00
Evgeny 3217f744f9 memory_pool_is_freed + стратегические проверки на висячие указатели
memory_pool: memory_pool_is_freed(pool,obj) — поиск obj в free-списке

tw_pcbs (3 места): !ctx→halt и is_freed→halt в начале итерации
  — ловит висячий PCB до re-alloc и после re-alloc

etcp: is_freed→halt перед etcp_conn_input (caller + callee)
  — ловит pkt освобождённый до входа

tcp_alloc: убран старый скан tw_pcbs (заменён на is_freed)
все dangling проверки — halt (while(1)) вместо break/return
2026-06-05 20:45:42 +03:00
Evgeny 24a5de9398 memory_pool: канарейка (0xAA) + счётчик double-free; lwip_tcp: убраны TCP_TW_MAX/tw_iter, добавлен хук в tcp_alloc
memory_pool: +2 байта за объектом — canary 0xAA для детекции buffer overflow,
counter для детекции double-free. При free проверяется canary и counter==0,
при alloc выставляется новый counter.

lwip_tcp: убраны TCP_TW_MAX и счётчики tw_iter (толку нет, краш на 2й итерации).
Оставлены: !pcb->ctx (dangling pointer), self-loop checks, next_owner диагностика.
Добавлена проверка в tcp_alloc: если новый PCB найден в tw_pcbs → halt.
2026-06-05 20:01:28 +03:00
Evgeny a09c647dfd lwip_tcp: детект и защита от self-loop (pcb->next==pcb) в tw_pcbs
tcp_slowtmr: после tcp_free проверка pcb==pcb2 → ctx->tw_pcbs=NULL, break
tcp_kill_timewait: проверка inactive->next==inactive → ctx->tw_pcbs=NULL, tcp_free
lwip_tcp_input: проверка pcb->next==pcb перед tcp_timewait_input → tcp_abort
все 3 лога выводят next_owner для диагностики источника self-ссылки
2026-06-05 18:15:43 +03:00
Evgeny 11002bcb57 lwip_tcp: защита от зацикливания tw_pcbs + next_owner диагностика + таймауты + фикс RTO
- TCP_TW_MAX=256: циклозащита в for/while циклах tw_pcbs (input, slowtmr, kill_timewait)
  + TCP_RMV макрос тоже защищён от бесконечного поиска
- next_owner в struct tcp_pcb/tcp_pcb_listen: отслеживает кто правил pcb->next
  (REG=1, RMV=2, SLOWTMR=3, INPUT=4). memset при alloc обнуляет
- таймауты: FIN_WAIT 20s→6s, SYN_RCVD 20s→6s, MSL 60s→25s (TIME_WAIT=50s)
- tcp_alloc: RTO из ctx->rto_min_ms вместо хардкода 3000
- handle_fin: pcb_state в лог
2026-06-05 17:19:50 +03:00
Evgeny 7a35f4c386 proxy client: привести lwIP TCP close к лучшим практикам
handle_close: tcp_abort→tcp_close + полное обнуление коллбэков (graceful FIN вместо RST)
handle_error: отделён от handle_close, свой cleanup с tcp_abort (RST — нештатная ситуация)
poll_cb/destroy: полное обнуление коллбэков перед tcp_abort (tcp_arg/recv/sent/err/poll)
handle_fin: исправлен лог (shutdown write, не read)
tx_waiter_cb: проверка pc->pcb перед tcp_recved
2026-06-05 15:33:17 +03:00
Evgeny 483bf440fb tcp_proxy_client: skip tcp_shutdown on TIME_WAIT/CLOSED pcb
handle_fin вызывает tcp_shutdown(pcb,0,1) на любом PCB, включая TIME_WAIT.
При active close (FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT) PCB уже в tw_pcbs,
tcp_shutdown на нём портит состояние → tw_pcbs зацикливается → lwip_tcp_input зависает.

Проверка state != TIME_WAIT && != CLOSED перед вызовом.
2026-06-05 12:45:51 +03:00
Evgeny 6f1916b6b2 tcp_proxy_client: tcp_close→tcp_abort, fix tw_pcbs loop
При CLOSE и FIN от сервера в обратном порядке (FIN relay + CLOSE от двух FIN),
клиент сначала обрабатывает CLOSE (tcp_close → TIME_WAIT), потом FIN
(tcp_shutdown на TIME_WAIT pcb), что портит tw_pcbs список — pcb->next
замыкается на себя, lwip_tcp_input зависает в бесконечном цикле.

tcp_abort шлёт RST и удаляет pcb сразу, без TIME_WAIT.
2026-06-05 12:25:46 +03:00
Evgeny 01dbd76834 1 2026-06-05 00:17:17 +03:00
Evgeny 6ee030ca7c 1 2026-06-04 23:07:34 +03:00
Evgeny 3ae4988353 1 2026-06-04 22:39:02 +03:00
Evgeny 57281a975e 1 2026-06-04 21:59:40 +03:00
Evgeny b953a6daf0 tcp_io: FIN/close через write_queue, отладка всех этапов закрытия
- tcp_io.h/c: push_fin/push_close через очередь отправки
  Кодирование: dgram=NULL→close, len=0+dgram!=NULL→FIN (sentinel)
  FIN: shutdown(SHUT_WR), стоп чтения, on_fin_sent
  CLOSE: close сокета, on_closed
  read_cb: отбрасывание входящих после fin_sent
  guard wq_fetch: error||closed (не fin)
- tcp_proxy_server: адаптация handle_fin→push_fin, handle_close→push_close
  read_queue_drain_cb: discard при fin_sent
  DEBUG_INFO на всех точках закрытия
- tcp_proxy_client: send_fin, handle_fin — DEBUG_INFO
- test_tcp_io: +6 тестов (push_fin, push_close, ordering, double-push, fin+close)
2026-06-04 19:20:20 +03:00
Evgeny 612b6bf1bf tcp_io: add DEBUG_TRACE for POLLOUT/busy-loop diagnostics 2026-06-04 16:37:07 +03:00
Evgeny 58e45e9232 proxy: implement CLOSE and FIN relay protocol
- CLOSE: admin connection teardown (drain queues, shutdown, free)
  Server: handle_close — pause read, drain rq (discard), drain wq to socket, shutdown, free
  Client: handle_close — drain to_lwip/tx_buf (discard), tcp_close pcb, free
  ERROR calls handle_close on both sides
- FIN: transparent relay event through ETCP (TCP_PROXY_SUBCMD_FIN=0x06)
  Server: on_fin_cb relays FIN_MSG to client, on_flushed_cb relays FIN_MSG
  Server: handle_fin relays FIN to remote socket via shutdown(SHUT_WR)
  Client: recv_cb relays FIN_MSG to exit, tx_waiter_cb relays FIN_MSG
  Client: handle_fin relays FIN to local TCP via tcp_shutdown(pcb,0,1)
- new tcp_conn_pause_read() in tcp_io (EPOLLIN off + cancel waiter)
- poll_cb simplified: remove tun_closed&&rem_closed wait block
- conn_free drains close_pending before free on both sides
- handle_data WARN→INFO for drops after close
- tcp_shutdown declared in lwip_tcp.h
2026-06-04 16:02:32 +03:00
Evgeny f8111f7f03 tcp_proxy_client: move PROXY FEED per-packet log from DEBUG to TRACE 2026-06-04 12:26:27 +03:00
Evgeny 859290c202 etcp_router: move per-packet ETCP_SEND/ETCP_RECV/ACK_SEND/queued logs from DEBUG to TRACE 2026-06-04 12:24:15 +03:00
Evgeny cfd885de7c tcp_proxy_server: move per-packet SOCK:RECV log from INFO to TRACE 2026-06-04 12:20:10 +03:00
Evgeny 8d0c40195b tcp proxy/lwip: move per-packet traffic logs from INFO/ERROR to TRACE 2026-06-04 12:13:50 +03:00
Evgeny e765d62e43 etcp_route_send: add force parameter to bypass send_q limit for control messages
CLOSE/ERROR/CONNECT and routing messages use force=1 to never be
dropped by backpressure. Regular DATA uses force=0 (default).
2026-06-04 12:01:55 +03:00
Evgeny c56ec0b0ed uasync: добавить отладку poll→fd/rev, timer→имя при разрешении событий 2026-06-04 01:57:10 +03:00
Evgeny 4dfc0ce947 fix: correct INIT padding — include SC_NONCE_SIZE + SC_CRC32_SIZE in formula
REQUEST: s_max = mtu - SC_NONCE_SIZE - 3 - SC_CRC32_SIZE - SC_TAG_SIZE
                 - SC_PUBKEY_ENC_SIZE + UDP_SC_HDR_SIZE (= mtu-78, was mtu-61)
RESPONSE: s_max = mtu (noencrypt=0, simple cap)

Removed debug logs.
2026-06-04 01:31:39 +03:00
Evgeny b8da0db1ab debug: add final offset/to_add/data_len log before encrypt_send
INIT_REQ/INIT_RESP now log offset, s, to_add, data_len, noencrypt_len
right before etcp_encrypt_send to trace why enc_buf exceeds mtu.
2026-06-04 01:17:32 +03:00
Evgeny 30acde7b18 debug: add ERROR-level logs for INIT padding calc (req+resp)
Shows mtu, s_rand, s_max, offset/xoffset, and whether capped.
RESP: removed old hardcoded 1472+26 cap, replaced by formula.
Both paths use consistent formula based on #defines.
2026-06-04 01:13:15 +03:00
Evgeny c0b6e5769e fix: correct INIT padding cap using defines instead of hardcoded 19
Formula: s_max = mtu - 3 - SC_TAG_SIZE + UDP_SC_HDR_SIZE - SC_PUBKEY_ENC_SIZE
For mtu=1460: 1460 - 3 - 16 + 30 - 72 = 1399 (was 1390, off by 9)
2026-06-04 00:57:19 +03:00
Evgeny 3c036ecc4c fix: restore conn_free cleanup — tcp_conn_destroy, timers, waiters were lost
Previous edit accidentally removed tcp_conn_destroy, timer cancellations,
and etcp_router_cancel_send_ready from conn_free. Without tcp_conn_destroy
the socket was never removed from uasync, causing repeated EPOLLERR →
repeated conn_free on freed memory → double-free crash.

Added debug logs for freed guard entry and u_free.
2026-06-04 00:44:51 +03:00
Evgeny cb764a1143 fix: INIT padding cap computed from actual link MTU, not hardcoded 1472
Hardcoded 1472-46 cap exceeded link MTU=1460, causing 'packet too long'
error on INIT handshake. Use link->mtu - UDP_HDR_SIZE - offset - 19
instead of fixed 1472.
2026-06-04 00:33:29 +03:00
Evgeny 0c8de3db84 fix: double-free in tcp_proxy_server_conn_free via freed flag
error_cb can fire twice for the same fd in one epoll batch,
or conn_free called from multiple callbacks during restart/shutdown.
freed flag makes conn_free idempotent.
2026-06-04 00:13:28 +03:00
Evgeny 4ba790b921 fix: SIGSEGV in tcp_proxy_client_destroy — abort pcbs before lwip destroy
Move lwip_tcp_destroy after pcb cleanup to avoid aborting pcbs
with already-freed context. Add state != CLOSED guard to tcp_abort.
2026-06-04 00:02:47 +03:00
Evgeny e0378ebf1d refactor: remove consumer_ack, isolate proxy from router internals
etcp_router:
  - removed consumer_ack field, consumer_ack function, consumer_ack logic
  - ACK always auto-sent via router_schedule_ack (10ms throttle)
  - conn_restart: local-only reset, no remote CLOSE, unified close notification (len=9)
  - START detection: only when rx_seq>0 (first conn doesn't trigger)
  - new API: etcp_router_on_send_ready / cancel_send_ready for send_q backpressure

tcp_proxy_server:
  - removed write_on_get_cb, queue_set_on_get, rconn->consumer_ack
  - removed etcp_router_conn_get, direct send_q access
  - uses etcp_router_on_send_ready for backpressure

tcp_proxy_client:
  - removed consumer_ack call
  - unified conn=NULL handler (len=9 = close/restart with node_id)

protocol: close/restart notification now len=9 (svc_id + node_id), no RESTART subcmd
2026-06-03 23:46:11 +03:00
Evgeny d784b2bfbe fix: use-after-free in tcp_conn_destroy + throttle consumer_ack
tcp_conn_destroy: set read/write queue callbacks to NULL before
queue_free to stop deferred autofetch (drain_cb on freed rc).

write_on_get_cb: throttle consumer_ack to every 8th write_queue
dequeue instead of every single one (was 30+ ACK/sec).

ack_batch field in tcp_proxy_server_conn.
2026-06-03 22:47:23 +03:00
Evgeny 0449a5fd9d test: 5 new router unit tests — RST, restart, sess_id
test_rst_flag: inject ROUTER_FLAG_RST → rconn destroyed, service notified
test_client_restart: pump random data → inject START → restart detected
test_sess_id_change: pump sess_id=0 → inject sess_id=1+START → detected
test_sess_id_wrap: pump sess_id=3, peer_sess_id=3 → inject sess_id=0+START → wrap detected
test_server_reinit: reinit router → new rconn start_sent=0, sess_id=0

inject() extended with uint8_t flags parameter.
pump_data helper for random data injection.
test_handler tracks g_notify_count for close/restart notifications.

fix: etcp_router_recv_cb — return after RST flag to avoid NULL rconn deref
2026-06-03 21:56:11 +03:00
Evgeny d0029178e7 feat: 1-byte flags in SVC_ROUTE_HDR — START, RST, 2-bit sess_id
SVC_ROUTE_HDR: +uint8_t flags (23 bytes total).
  bit7=START (0x80) — first data packet of new session
  bit6=RST (0x40) — sequence violation detected
  bit5-4=sess_id (0-3) — cyclically incremented on restart

router_send_one_flags: sets START+sess_id on first data packet.
router_send_ack: sets sess_id only (no START for ACK).
etcp_router_recv_cb: detects restart via sess_id change or START flag.
  Old seq=0 heuristic replaced with explicit flags.
etcp_router_conn_get: init sess_id=0, peer_sess_id=0, start_sent=0.
etcp_router_conn_restart: increment sess_id before closing rconn.
2026-06-03 21:20:05 +03:00
Evgeny ca7f6c0d01 feat: peer restart detection + etcp_router_conn_restart with service notification
etcp_router: new etcp_router_conn_restart(inst, node_id, svc_id) — closes
all rconns for the peer, notifies service via cb(NULL, entry) with
node_id, resets seq state.

seq=0 detection in etcp_router_recv_cb: when rx_seq >= 256 (MAX_INFLIGHT)
and a data packet arrives with seq=0, detect peer restart and reset rconn.

tcp_proxy_client: handle TCP_PROXY_SUBCMD_RESTART — clear all client conns
and server conns for the restarted peer.

Threshold prevents false positives on in-window seq=0 duplicates.
2026-06-03 20:45:42 +03:00
Evgeny 609be50b8f feat: ETCP trace logging — ETCP_SEND, ETCP_RECV, SEND_Q_DRAIN, SEND_Q_ACKED, CONSUMER_ACK, ACK_SEND
Six key log points at DEBUG level to trace the full ACK chain:
  ETCP_SEND  — server sends direction A data
  ETCP_RECV  — received any svc_route packet
  SEND_Q_DRAIN — send_q drained after client ACK
  SEND_Q_ACKED — server received client ACK (direction A)
  CONSUMER_ACK  — server sends consumer ACK (direction B)
  ACK_SEND      — low-level ACK packet sent
2026-06-03 19:27:31 +03:00
Evgeny e4e5ed71f8 fix: etcp_router_consumer_ack — use router_ack_do_send instead of schedule
router_schedule_ack skips when rx_seq==last_sent_ack_seq (no-op),
so the first consumer_ack call after setting consumer_ack=1 was
silently ignored. Client never got ACK → send_q stuck at 256.

router_ack_do_send always sends the ACK unconditionally.
2026-06-03 19:20:06 +03:00
Evgeny 4716641dff fix: initial consumer_ack in handle_connect + diag: send_q, BACKPRESSURE, ACK logs
handle_connect: send first consumer_ack after setting consumer_ack=1
to unblock client router — fixes rq=32/send_q=256 deadlock.

diag_timer_cb: added send_q=N to DIAG line for instant root cause visibility.
read_queue_drain_cb: SOCK:BACKPRESSURE log with send_q count.
write_on_get_cb: SOCK:ACK debug log on each consumer_ack.
2026-06-03 19:07:06 +03:00
Evgeny bbf39c091d feat: send_q backpressure + ll_queue on_get callback + consumer_ack flow control
etcp_router: ROUTER_MAX_SEND_Q_PACKETS=256, router_enqueue_send returns
error when send_q full, etcp_route_send propagates -1 on backpressure.

ll_queue: queue_set_on_get — deferred callback after each queue_data_get.
Replaces manual consumer_ack polling with queue-driven notification.

tcp_proxy_server: consumer_ack=1, write_on_get_cb triggers ACK on
each write_queue dequeue. Full backpressure chain:
  write_queue full → no dequeue → no ACK → sender stops → all queues pause
  write_queue drains → dequeue → on_get → ACK → sender resumes
2026-06-03 18:51:12 +03:00
Evgeny a0da29f1eb fix: simplify diag_timer_cb — use pool->free_count instead of allocations-reuse
DIAG line now shows clear metrics:
  rq/wq — queue sizes
  ent_f/dat_f — free_count of entry/data pools (→0 = leak)
  wbuf — partial send stuck
  tcp_rb/tcp_sb — kernel socket buffers
2026-06-03 17:58:08 +03:00
Evgeny dc2b394e7a feat: per-connection diagnostic timer in tcp_proxy_server
1-sec diag_timer_cb prints rq/wq counts+bytes, outstanding pool
allocations, tcp kernel buffer sizes (SO_RCVBUF/SO_SNDBUF).
Started in handle_connect, cancelled in conn_free.
2026-06-03 17:36:45 +03:00
Evgeny 04f6b3e040 refactor: remove tcp_conn_push_write, use direct queue put with chunking
External code pushes to write_queue directly (entry_pool + data_pool).
Added queue_set_threshold(write_queue, 32, 0) for write backpressure.
Tests split data into write_chunk_size chunks before queue put.
2026-06-03 16:38:09 +03:00
Evgeny 63f019530b feat: tcp_io library + uasync socket toggle + ll_queue empty_callback
lib/u_async: uasync_set_socket_read/write — O(1) EPOLL_CTL_MOD toggle per socket
lib/ll_queue: queue_set_empty_callback — one-shot deferred callback on queue drain
lib/tcp_io: TCP connection management via uasync + ll_queue with pool alloc
  - read: direct recv into pool buffer, high/low water flow control
  - write: autofetch via ll_queue callback, partial send via write_buf
  - on_fin/on_error/on_flushed callbacks, FIN deferred until read_queue drained
  - getpeername for pre-connected socket detection
tcp_proxy_server: migrated to tcp_io (22→13 fields, 437→270 lines, -7 functions)
tests: test_tcp_io (5 tests), test_tcp_proxy_server rewritten for tcp_conn
2026-06-03 16:24:46 +03:00
Evgeny 3533ec3122 fix: memory leaks in TCP proxy server/client
- tcp_proxy_server: close socket + conn_count-- on uasync_add_socket_t failure
- tcp_proxy_server: conn_count-- on connect() failure
- tcp_proxy_client: queue_free(to_lwip) + tcp_abort on send_connect failure
- tcp_proxy_client: free tx_buf + cancel tx_waiter in destroy loop
2026-06-03 12:55:00 +03:00
Evgeny ac3fe79db8 etcp_router: consumer-driven ACK with interval throttle (10ms)
- consumer_ack flag: ACK sent only on consumption, not assembly
- etcp_router_consumer_ack() — called from tcp_proxy_client feed_from_transport
- last_ack_sent_tb: interval-based throttle — send immediately if >=10ms passed,
  otherwise timer for remaining time
- timer rules: NULL handle after cancel/fire, NULL check before start
- ROUTER_ACK_INTERVAL_TB 1000→100 (100ms→10ms)
- test_etcp_router_unit: updated test 14 for new immediate-send behavior
2026-06-03 11:50:50 +03:00
Evgeny e7eb24537d fix: proxy ERROR handling — single CLOSE on error, no ERROR response cascade
- tcp_proxy_client_handle_error: set error flag + send CLOSE once if flag was clear
- tcp_proxy_client_handle_data/handle_close: drop silently, don't send ERROR back
- tcp_proxy_server_handle_data/handle_close: drop silently, don't send ERROR back
- Add recv→send 1MB integration test (3 clients × 3 requests, shell + python)
2026-06-03 10:36:55 +03:00
Evgeny 51d5790938 fix: remove tautological BBR_EXTRA_ACKED_GAIN checks 2026-06-03 02:21:26 +03:00
Evgeny 2a259191b9 feat: queue_set_waiter_defer — immediate or call_soon callback mode
queue_waiter_handle stores defer_q/cb/arg, no extra malloc.
queue_set_waiter_defer(q, 1) enables call_soon dispatch for waiter callbacks.
bbr_integration uses it instead of manual send_waiter_cb wrapper.
2026-06-03 02:18:46 +03:00
Evgeny 331d43c6f7 fix: proxy ERROR loop, BBR test backpressure, O(1) u_free, test ordering
- tcp_proxy: ERROR silently dropped (no ERROR->ERROR loop), Russian removed from logs
- etcp_bbr: swap min(lo) and max(4*mss) in bbr_bound_cwnd_for_inflight_model
- test_etcp_bbr: fix cwnd comparison (packets vs bytes)
- ll_queue: double-put detection (abort), Floyd cycle detection in consistency check
- ll_queue: QUEUE_DEBUG commented out (was always-on, O(N) per put)
- mem: u_free_impl now O(1) via doubly-linked list, double-free detection
- bbr_integration: queue_waiter_wait + call_soon backpressure (threshold=1)
- tests/Makefile.am: fast tests first, *.log cleanup before run
2026-06-03 02:06:09 +03:00
Evgeny 7f5325ac24 etcp_router: fix inflight underflow by splitting rx_acked into tx_acked + last_sent_ack_seq
rx_acked was used for two conflicting purposes:
1. remote ack of our sends (inflight = tx_seq - rx_acked)
2. our last sent ACK seq (dedup: rx_seq != rx_acked)

When the ack timer fired and set rx_acked = rx_seq, it overwrote
the inflight-tracking value. If rx_seq > tx_seq, the computation
tx_seq - rx_acked underflowed (e.g. 18 - 24 = 0xFFFFFFFA),
permanently blocking router_drain_send_q and causing send_q to
grow indefinitely.

Fix:
- Split rx_acked into tx_acked (remote ack, for inflight) and
  last_sent_ack_seq (our ACK, for dedup)
- Incoming ACK handler only advances tx_acked forward (stale guard)
- Use int32_t cast on all inflight comparisons to handle stale states
- Add etcp_router architecture diagram (doc/etcp_router_arch.md)
2026-06-02 22:38:20 +03:00
Evgeny 3a2b427287 etcpmon: widen debug level labels by 30% and shift radio buttons right 2026-06-02 22:16:37 +03:00
Evgeny 43baeb2306 etcpmon: fix autoradio button groups and add missing ETCPROUTE category 2026-06-02 22:13:18 +03:00
Evgeny fceec1aea6 fix: move debug logs before async operations (queue_put, timer_set, resume) for meaningful log ordering 2026-06-02 21:53:51 +03:00
Evgeny c5c4967b69 1 2026-06-02 21:19:07 +03:00
Evgeny 4556f327c2 1 2026-06-02 21:05:06 +03:00
Evgeny 226a5744b5 etcpmon: Debug Levels block moved to right column, global radio labels removed, columns narrowed 2026-06-02 19:24:49 +03:00
Evgeny 1f4fe37fe5 proxy: rename + simplify + backpressure
- tcp_proxy/remote_proxy → tcp_proxy_client/tcp_proxy_server
- Все proxy файлы перенесены в src/proxy/
- Конфиг: [tcp_proxy]→[tcp_proxy_client], [remote_proxy]→[tcp_proxy_server]
- Убран subcmd CONNECTED (0x02) — клиент шлёт DATA сразу после CONNECT
- Backpressure: etcp_router_waiter_register/cancel через ll_queue threshold waiter
- Клиент: recv_cb send fail → tx_buf+waiter, не tcp_recved() (окно lwIP=0)
- Сервер: read_cb send fail → pause_buf+waiter, пауза read_id
- CLOSE/ERROR отправляется только после drain всех очередей
- Fail: conn не найден/closed → send ERROR вместо CLOSE/silent drop
- PROTOCOL.md: полная архитектура с диаграммами состояний и потоков
2026-06-02 18:02:37 +03:00
Evgeny e7a9163011 remote_proxy: гарантированная доставка CLOSE/ERROR обратно (exit→client) через retry-таймер
- remote_proxy_conn: close_pending, close_timer, close_backoff
- rp_send_close/rp_send_error: при сбое etcp_route_send → close_pending=1 + таймер с backoff
- rp_close_retry_cb: повторная отправка CLOSE/ERROR с экспоненциальным backoff (50→5000 tb)
- rp_conn_free: очистка close_timer
2026-06-01 23:31:39 +03:00
Evgeny b43de4c1f0 tcp_proxy: гарантированная доставка CLOSE/ERROR через retry при сбое маршрута
- proxy_conn.close_pending: флаг недоставленного CLOSE/ERROR
- send_close/send_error: при возврате -1 выставляют close_pending=1
- proxy_poll_cb: периодический retry close_pending → CLOSE дойдёт при восстановлении ETCP
- proxy_recv_cb: проверка !close_pending перед повторной отправкой close
2026-06-01 23:26:15 +03:00
Evgeny 60800a7f35 tcp_proxy: не слать данные в lwip после закрытия TUN-стороны (tun_closed)
- handle_data: добавить pc->tun_closed в проверку сброса данных от exit
- proxy_feed_from_transport: не писать в PCB после tun_closed
- предотвращает ERR_RST (-14) при записи в TIME_WAIT PCB
2026-06-01 22:59:20 +03:00
Evgeny f43631a532 etcpmon: исправить синтаксис — orphaned код перенесён внутрь функции etcpmon_client_add_to_history 2026-06-01 20:31:52 +03:00
Evgeny 6d661360b1 etcp: адаптивный keepalive — отдельная команда ETCP_KEEPALIVE с period_ms
- ETCP_KEEPALIVE=0x08, формат: [0x08][period_lo][period_hi]
- link->ka_period_ms: 200ms при трафике, ×1.05+1 в idle, потолок 10s
- keepalive_timeout = peer_period * KA_TIMEOUT_MULT(10)
- приём KA обновляет keepalive_timeout адаптивно (2000ms→100000ms)
- флаг recv_keepalive по-прежнему в flag_up для быстрого сигнала
2026-06-01 20:15:06 +03:00
Evgeny b2e72db31b etcp: link_status = remote_keepalive && recv_keepalive (вместо только remote)
- link_status теперь требует двусторонней слышимости: remote && local
- recv_keepalive восстанавливается ДО вычисления link_status (избегает заклинивания)
- loadbalancer_link_ready по переходу 0→1 с проверкой was_up
2026-06-01 19:43:39 +03:00
Evgeny c5b4ec7a05 bbr: исправить бесконечную рекурсию в bbr_now — bbr_now(bbr) → get_time_tb() 2026-06-01 18:28:02 +03:00
Evgeny 0adff0253d bbr: исправить отображение сырых BW-значений (bw_latest/bw_lo/bw_hi) с /125 на /2097
BW_RAW_TO_KBPS = bw_raw * 8000 / BW_UNIT = bw_raw / 2097.152
pacing_rate / 125 остаётся без изменений (уже в bytes/sec после bbr_rate_bytes_per_sec)
2026-06-01 18:13:07 +03:00
Evgeny 4f0baa37db bbr: исправить передачу потерь в BBR + пол для cwnd на уровне BBR_CWND_MIN_TARGET*mss
- etcp.c: rs->lost из link->bbr_loss_since_ack (был хардкод 0), вызов bbr_note_loss при ретрансмите
- etcp_connections.h: поле bbr_loss_since_ack для per-link учёта потерь в байтах
- etcp_bbr.c: везде где BBR_CWND_MIN_TARGET — умножить на mss (юнит-мизматч пакеты/байты)
  - bbr_quantization_budget, bbr_set_cwnd, bbr_bound_cwnd_for_inflight_model, bbr_probe_rtt_cwnd
  - bbr_init_lower_bounds: инициализация inflight_hi из cwnd (был ~0U навсегда)
- control_server.c + etcpmon: экспорт BBR-метрик (bw_hi/lo, inflight_hi/lo, режим, потери)
- tests/bbr_integration: интеграционный тест BBR (15с, 50ms/5%loss/1Mbit) с таблицей метрик
- устранён death spiral BBR при высоких потерях: cwnd теперь не падает ниже 4*mss
2026-06-01 17:50:16 +03:00
Evgeny 6c79939db3 congestion: заменить AIMD на BBR v3 (per-link), добавить юнит-тесты BBR
- Новые файлы: src/etcp_bbr.h/c — полный порт BBR v3 из BBR/bbr_v3.c (референс сохранён)
- struct ETCP_LINK: удалены AIMD-поля (rtt_history, rtt_swm, stat_win, stats_timer, inflight_phase...), добавлены bbr*, delivered_bytes, bbr_pacing_rate
- struct INFLIGHT_PACKET: +3 поля для rate_sample (delivered_at_send, inflight_at_send, is_app_limited)
- etcp_ack_recv: вызов bbr_main() с rate_sample, обновление inflight_lim_bytes и pacing
- etcp_request_pkt: заполнение BBR rate_sample полей при отправке
- Шейпер: bandwidth обновляется из bbr_pacing_rate
- Конфиг: inflight_min/max заменены на bbr_max_cwnd
- Дамп: BBR поля (mode, cycle, pacing, min_rtt_us, bw_hi/lo, inflight_hi/lo)
- Time injection: bbr->now_tb для детерминированных тестов
- tests/test_etcp_bbr.c: 8 юнит-тестов (init, startup, full_bw/drain, loss, cwnd cap, loss cut, fast_path, probe_rtt)
- Отключены test_etcp_congestion и test_etcp_reinit_inflight (старый congestion control)
- 40/40 тестов пройдено
2026-06-01 12:58:31 +03:00
93 changed files with 10086 additions and 2307 deletions

No files matched your search

+1
View File
@@ -108,3 +108,4 @@ cl
build_win.log
/wintun.dll
src/lwip_orig/
lib/timeout/
+6
View File
@@ -289,6 +289,11 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
## Queue Usage Rules
## Работа с очередями. Важно!
С очередью следуею работать придерживаясь следующих правил:
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
### Запись в очередь
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
@@ -314,6 +319,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
## Bugs Debugging Protocol
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
### Прочие правила:
- sed для редактирования исходников - запрещено
+324
View File
@@ -0,0 +1,324 @@
# ETCP Router Architecture
## Структуры данных
```
ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id)
┌─────────────────────────────────────────────────────┐
│ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │
│ remote_node_id: u64 │
│ svc_id: u8 │
│ last_dgram_ts: u16 (timestamp последней датаграммы) │
│ │
│ tx_seq: u32 следующий seq для отправки │
│ rx_seq: u32 ожидаемый seq для сборки │
│ tx_acked: u32 сколько наших пакетов подтвердил remote │
│ last_sent_ack_seq: u32 последний отправленный ACK │
│ │
│ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │
│ send_q: ll_queue* очередь при переполнении inflight │
│ │
│ ack_timer: void* периодический 100ms │
│ idle_ack_timer: void* idle таймаут 500ms │
│ send_resume_timer: void* retry send_q 50ms │
│ send_blocked: u8 inflight полон │
└─────────────────────────────────────────────────────┘
SVC_ROUTE_HDR (22 байта)
┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐
│ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │
│ u8 │ u64 │ u64 │ u32 │ u8 │ ... │
└──────┴────────────────┴────────────────┴───────┴────────┴──────────┘
seq в data-пакетах = tx_seq (счетчик отправленных)
seq в ACK-пакетах = rx_seq (ожидаемый следующий)
CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000
```
## Send Path (отправка данных)
```
etcp_route_send(inst, dst, entry)
etcp_router_conn_send(rconn, data, len)
│
▼
┌─────────────────────┐
│ rconn = find/get │ поиск по (dst, svc_id)
│ авто-создание если │ или etcp_router_conn_get()
│ ещё нет │
└────────┬────────────┘
│
▼
┌──────────────────────────────┐
│ inflight < ROUTER_MAX(256)? │
│ tx_seq - tx_acked < 256 ? │
└──────┬───────────┬───────────┘
│ YES │ NO (inflight полон)
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO)
│ (прямая) │ │ (в очередь) │ send_blocked = 1
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms)
│ │
▼ │
┌────────────────────┐ │
│ tx_seq++ │ │
│ hdr.dst = dst │ │
│ hdr.src = node_id │ │
│ hdr.seq = tx_seq │ │
│ hdr.svc_id = svc │ │
│ │ │
│ etcp_send(conn, e) │ │
└────────────────────┘ │
│
┌────────────────────────────┘
│ (при получении ACK от remote)
▼
┌────────────────────┐
│ router_drain_send_q│ вызывается из ACK handler
│ │ или по send_resume_timer
│ while inflight < 256│
│ e = send_q.pop() │
│ router_send_one(e)│
│ │
│ если send_q пуст: │
│ send_blocked = 0 │
└────────────────────┘
```
## Receive Path (приём данных)
```
etcp_router_recv_cb(conn, entry)
│
▼
┌──────────────────────┐
│ dst == node_id? │
└──┬───────────────┬───┘
│ YES │ NO (транзит)
▼ ▼
┌────────────────────┐ ┌──────────────────────────┐
│ Мы — целевая нода │ │ Транзит: найти next hop │
└────────┬───────────┘ │ route_bgp_find_conn_for_ │
│ │ node(bgp, hdr.dst) │
▼ │ etcp_send(next, entry) │
┌────────────────────┐ └──────────────────────────┘
│ pl_len == 0? │
└──┬───────────┬─────┘
│ YES │ NO (данные)
▼ ▼
┌─────────┐ ┌─────────────────────────┐
│ CLOSE/ │ │ rconn = find/get │
│ RST? │ │ (авто-создание если нет) │
└──┬──┬───┘ └───────────┬─────────────┘
│ │ │
│ │ NO (ACK) ▼
│ ▼ ┌───────────────────────────────────────┐
│ ┌──────────┐│ Проверка границ seq: │
│ │ tx_acked ││ d = seq - rx_seq │
│ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │
│ │ (только ││ │
│ │ вперёд) ││ Дубликат? │
│ │ drain_q ││ rx_seq - seq > 0 ИЛИ │
│ └──────────┘│ seq уже в recv_q? │
│ └──────┬─────────────┬──────────────────┘
│ │ OK │ out-of-bounds / dup
│ ▼ ▼
│ ┌──────────────┐ ┌─────────┐
│ │ recv_q.put() │ │ DROP │
│ │ хеш по seq │ └─────────┘
│ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────────────┐
│ │ seq == rx_seq? │
│ └──┬───────────────┬───┘
│ │ YES │ NO
│ ▼ │
│ ┌──────────────┐ │
│ │ try_assembly │ │
│ │ while next в │ │
│ │ recv_q: │ │
│ │ deliver() │ │
│ │ rx_seq++ │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┴──┐
│ │ router_deliver() │
│ │ вызывает callback │
│ │ сервиса (svc_id) │
│ │ entry: [svc_id|payload] │
│ └──────────────┬──────────┘
│ │
▼ ▼
┌────────────────────────────────────┐
│ router_schedule_ack(rconn) │
│ запускает ack_timer (100ms) если │
│ ещё не запущен │
└────────────────────────────────────┘
```
```
CLOSE / RST ветка:
┌────────────────────────────────────────┐
│ pl_len == 0 && seq & CLOSE/RST │
│ → router_close_and_notify(rconn) │
│ ├─ router_send_close_to_service() │
│ │ (callback с NULL conn) │
│ ├─ отправить CLOSE удалённой стороне│
│ ├─ отмена всех таймеров │
│ ├─ очистка recv_q + send_q │
│ └─ удаление из inst->router_conns │
└────────────────────────────────────────┘
```
## ACK Mechanism
```
┌─────────────────────────────────────────┐
│ router_schedule_ack(rconn) │
│ (вызывается при получении данных) │
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ если idle_ack_timer активен — отменить │
│ если ack_timer не активен — запустить │
│ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │
└────────────────┬────────────────────────┘
│
▼ (через 100ms)
┌─────────────────────────────────────────┐
│ router_ack_timer_cb() │
│ │
│ if rx_seq != last_sent_ack_seq: │
│ router_send_ack(rconn) │
│ last_sent_ack_seq = rx_seq │
│ перезапустить ack_timer │
│ else: │
│ запустить idle_ack_timer (500ms) │
└────────────────┬────────────────────────┘
│
▼ (через 500ms)
┌─────────────────────────────────────────┐
│ router_idle_ack_timer_cb() │
│ │
│ if rx_seq != last_sent_ack_seq: │
│ router_send_ack(rconn) (досылка) │
│ last_sent_ack_seq = rx_seq │
└─────────────────────────────────────────┘
router_send_ack(rconn):
hdr.seq = rx_seq
hdr.svc_id = svc_id
pl_len = 0 → это ACK-пакет
etcp_send(conn, entry)
```
```
Обработка входящего ACK:
┌────────────────────────────────────────┐
│ pl_len == 0 && нет CLOSE/RST флагов │
│ │
│ if seq >= tx_acked: // только вперёд │
│ tx_acked = seq // обновить │
│ else: │
│ DEBUG_WARN "stale ACK" │
│ │
│ if send_blocked: │
│ router_drain_send_q(rconn) │
└────────────────────────────────────────┘
```
## Inflight Control
```
ROUTER_MAX_INFLIGHT = 256
Отправка возможна когда: tx_seq - tx_acked < 256
Отправка заблокирована: tx_seq - tx_acked >= 256
При блокировке:
данные → send_q (FIFO, без дропов)
send_blocked = 1
send_resume_timer = 50ms retry
Разблокировка:
приход ACK от remote → tx_acked обновляется
→ router_drain_send_q() разгребает send_q
или send_resume_timer (50ms) → router_drain_send_q()
send_q полностью разобран → send_blocked = 0
```
## Полная схема потоков
```
┌──────────┐
│ Сервис │ (routing, proxy, NAT, ...)
│ (svc_id)│
└────┬─────┘
│
etcp_route_send() │ callback(svc_id, entry)
▼ ▲
┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐
│ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │
│ (выход) │ │ │ │ (вход) │
└─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘
│ │ send_q │ │ recv_q │ │
│ │ (FIFO) │ │ (хеш seq)│ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ Inflight / Assembly │ │
│ │ tx_seq - tx_acked │ │
│ │ rx_seq → deliver │ │
│ └───────────┬─────────────┘ │
│ │ │
└──────────────┼──────────────────┘
│
┌──────▼──────┐
│ BGP │
│ (нахождение│
│ next hop) │
└──────┬──────┘
│
┌──────▼──────┐
│ ETCP conn │──▶ сеть
└─────────────┘
──▶ send (исходящие данные)
──▶ recv (входящие данные, callback сервису)
──▶ транзит (через BGP к следующему hop)
```
## Таймеры (один rconn)
```
┌─────────────────────────────────────────────────────┐
│ │
│ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│
│ (периодический) │
│ │
│ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │
│ (досылка последнего ACK) │
│ │
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │
│ (retry когда send_q не пуст) │
│ │
└─────────────────────────────────────────────────────┘
```
## Флаги seq
```
┌──────────────────────────────────────────────────┐
│ CLOSE: 0x80000000 нормальное закрытие conn │
│ RST: 0x40000000 conn не найден (reset) │
│ │
│ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │
│ Data-пакеты: seq=tx_seq, pl_len>0 │
│ ACK-пакеты: seq=rx_seq, pl_len=0 │
└──────────────────────────────────────────────────┘
```
+3 -1
View File
@@ -26,7 +26,9 @@ libuasync_a_SOURCES = \
radix.c \
radix.h \
serialize.c \
serialize.h
serialize.h \
tcp_io.c \
tcp_io.h
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
+2
View File
@@ -127,6 +127,7 @@ static const struct {
{"general", DEBUG_CATEGORY_GENERAL},
{"nat", DEBUG_CATEGORY_NAT},
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -220,6 +221,7 @@ const char* debug_get_category_name(debug_category_t category_idx) {
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
case DEBUG_CATEGORY_NAT: return "NAT";
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
case DEBUG_CATEGORY_ETCPROUTE: return "ETCPROUTE";
case DEBUG_CATEGORY_NONE: return "NONE";
default: return "UNKNOWN";
}
+2 -1
View File
@@ -59,7 +59,8 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
#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_ETCPROUTE 22 // ETCP routing
#define DEBUG_CATEGORY_COUNT 23 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
+194 -43
View File
@@ -46,8 +46,23 @@ static inline void queue_check_thread(struct ll_queue* q) {
// Предварительные объявления внутренних функций
static void queue_resume_timeout_cb(void* arg);
static void check_waiters(struct ll_queue* q);
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
static void empty_trampoline(void* arg);
static void on_get_trampoline(void* arg);
static void empty_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
q->empty_call_soon_id = NULL;
queue_callback_fn cb = q->empty_callback;
void* cb_arg = q->empty_callback_arg;
q->empty_callback = NULL;
q->empty_callback_arg = NULL;
if (q->count == 0 && cb) cb(q, cb_arg);
}
static void on_get_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
if (q->on_get) q->on_get(q, q->on_get_arg);
}
// ==================== Управление очередью ====================
@@ -60,6 +75,9 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
q->name = name;
q->ua = ua;
q->size_limit = -1; // Без ограничения по умолчанию
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
q->waiter_defer = 0;
q->hash_size = hash_size;
q->index_offset = index_offset;
q->index_size = index_size;
@@ -112,6 +130,17 @@ void queue_free(struct ll_queue* q) {
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
// Это упрощает архитектуру и предотвращает double-u_free
// Освободить список waiters (сбросить handle->internal)
struct queue_waiter* w = q->waiter_head;
while (w) {
struct queue_waiter* next = w->next;
if (w->handle) w->handle->internal = NULL;
u_free(w);
w = next;
}
q->waiter_head = NULL;
q->waiter_tail = NULL;
// Освободить хеш-таблицу
if (q->hash_table) {
u_free(q->hash_table);
@@ -122,6 +151,14 @@ void queue_free(struct ll_queue* q) {
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
q->resume_timeout_id = NULL;
}
void* old_eid = q->empty_call_soon_id;
q->empty_call_soon_id = NULL;
if (old_eid) uasync_call_soon_cancel(q->ua, old_eid);
q->empty_callback = NULL;
q->empty_callback_arg = NULL;
q->on_get = NULL;
q->on_get_arg = NULL;
u_free(q);
}
@@ -266,24 +303,44 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
return hash;
}
// Проверить и запустить ожидающие коллбэки
static void waiter_defer_cb(void* arg) {
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
h->defer_cb(h->defer_q, h->defer_arg);
}
inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
struct queue_waiter_handle* h) {
if (q->waiter_defer) {
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
uasync_call_soon(q->ua, h, waiter_defer_cb);
} else {
cb(q, arg);
}
}
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
static void check_waiters(struct ll_queue* q) {
if (!q) return;
if (!q || !q->waiter_head) return;
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
const int cond = q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
if (!cond) return;
struct queue_waiter* waiter = &q->waiter;
struct queue_waiter* waiter = q->waiter_head;
q->waiter_head = waiter->next;
if (!q->waiter_head) q->waiter_tail = NULL;
if (waiter->callback) {
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
waiter->callback(q, waiter->callback_arg);
memset(waiter, 0, sizeof(*waiter));
}
struct queue_waiter_handle* h = waiter->handle;
if (h) h->internal = NULL;
queue_threshold_callback_fn cb = waiter->callback;
void* arg = waiter->callback_arg;
u_free(waiter);
if (cb) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
queue_waiter_call(q, cb, arg, h);
}
}
@@ -293,6 +350,12 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
abort();
}
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);
@@ -334,6 +397,12 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
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);
@@ -377,6 +446,12 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
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);
@@ -410,6 +485,12 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
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);
@@ -476,6 +557,12 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
// Проверить ожидающие коллбэки
check_waiters(q);
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
if (q->on_get)
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
#ifdef QUEUE_DEBUG
queue_check_consistency(q);// !!!! for debug
#endif
@@ -489,14 +576,27 @@ int queue_entry_count(struct ll_queue* q) {
// Функция проверки консистентности count и total_bytes
// Возвращает 0 если ok, -1 если есть несоответствия
int queue_check_consistency(struct ll_queue* q) {
if (!q) return -1; // Недопустимая очередь
if (!q) return -1;
// Проверка: если count > 0, то head не должен быть NULL
if (q->count > 0 && !q->head) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': count=%d but head is NULL!",
q->name ? q->name : "unknown", q->count);
return -1;
}
// Floyd cycle detection — за O(N) без риска зависания
{
struct ll_entry *slow = q->head, *fast = q->head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': CYCLE detected! entry=%p count=%d",
q->name ? q->name : "unknown", (void*)slow, q->count);
abort();
}
}
}
int actual_count = 0;
size_t actual_bytes = 0;
@@ -507,7 +607,6 @@ int queue_check_consistency(struct ll_queue* q) {
actual_bytes += current->int_len;
if (current->next) {
if (current->next->prev != current) {
// Несоответствие в связях prev/next
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': prev/next error at entry %p != %p entries: %d!=%d bytes: %zu!=%zu",
q->name ? q->name : "unknown", (void*)current, (void*)current->next->prev, actual_count, q->count, actual_bytes, q->total_bytes);
return -1;
@@ -532,34 +631,86 @@ int queue_check_consistency(struct ll_queue* q) {
return 0;
}
// ==================== Асинхронное ожидание ====================
// ==================== Пороговое ожидание (backpressure) ====================
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
struct queue_waiter* waiter = &q->waiter;
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк немедленно
callback(q, arg);
return NULL;
}
// Установить waiter для отложенного вызова
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
return waiter;
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
if (!q) return;
q->threshold_max_packets = max_packets;
q->threshold_max_bytes = max_bytes;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter || waiter != &q->waiter) return;
void queue_set_waiter_defer(struct ll_queue* q, int enable) {
if (!q) return;
q->waiter_defer = enable;
}
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
void* old = q->empty_call_soon_id;
q->empty_call_soon_id = NULL;
if (old) uasync_call_soon_cancel(q->ua, old);
q->empty_callback = cbk_fn;
q->empty_callback_arg = arg;
if (cbk_fn && q->count == 0)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
}
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->on_get = cbk_fn;
q->on_get_arg = arg;
}
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !h) return -1;
if (h->internal) queue_waiter_cancel(q, h);
if (q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
queue_waiter_call(q, callback, arg, h);
return 1;
}
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
if (!waiter) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
return -1;
}
waiter->next = NULL;
waiter->handle = h;
waiter->callback = callback;
waiter->callback_arg = arg;
h->internal = waiter;
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
q->waiter_tail = waiter;
return 0;
}
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
memset(waiter, 0, sizeof(*waiter));
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
struct queue_waiter* curr = q->waiter_head;
while (curr && curr != waiter) {
prev = curr;
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
h->internal = NULL;
return;
}
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
if (waiter == q->waiter_tail) q->waiter_tail = prev;
h->internal = NULL;
u_free(waiter);
}
// ==================== Поиск и удаление по ID ====================
+85 -20
View File
@@ -12,7 +12,7 @@
#include <pthread.h>
#endif
#define QUEUE_DEBUG 1
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
/**
@@ -26,7 +26,7 @@
* - Обязательный вызов queue_resume_callback после обработки элемента
* - Поддержка пулов памяти для entry и отдельно для dgram
* - Хеш-таблица для быстрого поиска по ID (опционально)
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
* - Связный список waiters для backpressure c общим порогом (round-robin)
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
*
* @note Важные правила использования автозабора (queue_set_callback):
@@ -37,13 +37,15 @@
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
*
* @note Память:
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
* (handle->internal каждого ожидающего сбрасывается в NULL)
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
*/
// Forward declaration
// Forward declarations
struct ll_queue;
struct queue_waiter_handle;
/**
* @struct ll_entry
@@ -84,7 +86,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
/**
* @typedef queue_threshold_callback_fn
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
* @brief Одноразовый коллбэк при достижении порога очереди.
* @param q указатель на очередь
* @param arg пользовательский аргумент
*/
@@ -92,13 +94,29 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
/**
* @struct queue_waiter
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
*/
struct queue_waiter {
int max_packets; ///< Максимально допустимое количество элементов
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
void* callback_arg; ///< Аргумент коллбэка
struct queue_waiter* next; ///< Следующий в списке ожидающих
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
queue_threshold_callback_fn callback;
void* callback_arg;
};
/**
* @struct queue_waiter_handle
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
*
* Вызывающая сторона:
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
*/
struct queue_waiter_handle {
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
struct ll_queue* defer_q;
queue_threshold_callback_fn defer_cb;
void* defer_arg;
};
/**
@@ -120,7 +138,18 @@ struct ll_queue {
void* resume_timeout_id; // ID таймера uasync для отложенного resume
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
struct queue_waiter waiter; // Встроенный waiter (только один)
struct queue_waiter* waiter_head; // Голова списка ожидающих
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
void* empty_callback_arg;
void* empty_call_soon_id; // handle от uasync_call_soon
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
void* on_get_arg;
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
@@ -190,26 +219,62 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание ==================== */
/* ==================== Пороговое ожидание (backpressure) ==================== */
/**
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
* @brief Устанавливает общий порог освобождения очереди.
* @param q очередь
* @param max_packets максимальное количество элементов
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
* @param max_bytes максимальный объём данных (0 = не проверять)
*
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
*/
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
void queue_set_waiter_defer(struct ll_queue* q, int enable);
/**
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
*/
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*/
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Регистрирует ожидание освобождения очереди до общего порога.
* @param q очередь
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
* @param callback функция, вызываемая при достижении порога
* @param arg аргумент коллбэка
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
*
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
*/
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
/**
* @brief Отменяет ранее установленное ожидание порога.
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
* @param q очередь
* @param waiter указатель, возвращённый queue_wait_threshold()
* @param h указатель на handle
*
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
*/
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */
+24 -39
View File
@@ -14,8 +14,9 @@
#define PADDING_FILL 0xAA
#define METADATA_SIZE 128
#define POINTER_SIZE sizeof(void*)
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE)
#define NEXT_OFFSET LOCATION_MAX
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
#define PREV_OFFSET LOCATION_MAX
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
#include <execinfo.h>
@@ -136,8 +137,12 @@ void* u_malloc_impl(uint32_t size, const char* location) {
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
// Add to list (thread-safe)
LOCK();
void** prev_ptr = (void**)(base + PREV_OFFSET);
void** next_ptr = (void**)(base + NEXT_OFFSET);
*prev_ptr = NULL;
*next_ptr = allocated_head;
if (allocated_head)
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base;
allocated_head = base;
allocated_count++;
allocations_count++;
@@ -175,29 +180,14 @@ void u_free_impl(void* ptr, const char* 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)
// Remove from doubly-linked list (O(1))
LOCK();
void* prev = NULL;
void* curr = allocated_head;
bool found = false;
while (curr) {
if (curr == base) {
if (prev) {
*(void**)((uint8_t*)prev + NEXT_OFFSET) = *(void**)((uint8_t*)curr + NEXT_OFFSET);
} else {
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
allocated_count--;
frees_count++;
found = true;
break;
}
prev = curr;
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
void* prev = *(void**)(base + PREV_OFFSET);
void* next = *(void**)(base + NEXT_OFFSET);
size_t active_count = allocated_count;
UNLOCK();
if (!found) {
if (prev == NULL && allocated_head != base) {
UNLOCK();
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
@@ -215,24 +205,19 @@ void u_free_impl(void* ptr, const char* location) {
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);
}
if (prev)
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next;
else
allocated_head = next;
if (next)
*(void**)((uint8_t*)next + PREV_OFFSET) = prev;
allocated_count--;
frees_count++;
UNLOCK();
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
memset(base, 0, total);
free(base);
+52 -6
View File
@@ -5,6 +5,37 @@
#include "memory_pool.h"
#include "mem.h"
#define POOL_CANARY_OFF (pool->object_size)
#define POOL_TAG_OFF (pool->object_size + 1)
#define POOL_ALLOC_SIZE (pool->object_size + 2)
#define POOL_CANARY_VAL 0xAA
static void pool_init_tags(struct memory_pool* pool, void* obj)
{
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
*canary = POOL_CANARY_VAL;
*counter = 1;
}
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
if (*canary != POOL_CANARY_VAL) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW %p canary=0x%02x from %s! halting", obj, *canary, location);
volatile int _halt = 1;
while (_halt) {}
}
if (*counter == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE %p from %s! halting", obj, location);
volatile int _halt = 1;
while (_halt) {}
}
*counter = 0;
}
static size_t g_total_free = 0;
size_t memory_pool_get_total_free_blocks(void) {
@@ -22,6 +53,7 @@ struct memory_pool* memory_pool_init(size_t object_size) {
pool->free_count = 0;
pool->allocations = 0;
pool->reuse_count = 0;
pool->alloc_tag_counter = 1;
return pool;
}
@@ -33,18 +65,21 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
if (pool->free_head) {
void* obj = pool->free_head;
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
pool->free_head = *(void**)obj;
pool->free_count--;
g_total_free--;
pool->reuse_count++;
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
memset(obj, 0, pool->object_size);
pool_init_tags(pool, obj);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
return obj;
}
pool->allocations++;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
return u_calloc_impl(1, pool->object_size, location); // Используем calloc для инициализации нулями
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
if (obj) pool_init_tags(pool, obj);
return obj;
}
// Освободить объект в пул или в free
@@ -53,9 +88,10 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
return;
}
// Если пул не заполнен, сохранить объект для повторного использования
pool_check_and_clear_tags(pool, obj, location);
if (pool->free_count < MEMORY_POOL_MAX_FREE) {
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
*(void**)obj = pool->free_head;
pool->free_head = obj;
pool->free_count++;
g_total_free++;
@@ -63,7 +99,6 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
return;
}
// Иначе освободить через free
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
u_free_impl(obj, location);
}
@@ -105,3 +140,14 @@ void memory_pool_destroy(struct memory_pool* pool) {
pool->free_count = 0;
u_free(pool);
}
int memory_pool_is_freed(struct memory_pool* pool, void* obj)
{
if (!pool || !obj) return 0;
void* cur = pool->free_head;
while (cur) {
if (cur == obj) return 1;
cur = *(void**)cur;
}
return 0;
}
+8 -5
View File
@@ -12,11 +12,12 @@
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
struct memory_pool {
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
int free_count; // Количество свободных блоков
size_t object_size; // Размер объектов в пуле
size_t allocations; // Общее количество аллокаций (включая новые malloc)
size_t reuse_count; // Количество повторных использований из пула
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
int free_count; // Количество свободных блоков
size_t object_size; // Размер объектов в пуле (пользовательские данные)
size_t allocations; // Общее количество аллокаций (включая новые malloc)
size_t reuse_count; // Количество повторных использований из пула
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
};
// сам пул:
@@ -33,4 +34,6 @@ 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)
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
#endif // MEMORY_POOL_H
+398
View File
@@ -0,0 +1,398 @@
// tcp_io.c — управление TCP-соединением через uasync + ll_queue
#include "tcp_io.h"
#include "debug_config.h"
#include "mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <fcntl.h>
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static void read_cb(socket_t sock, void* arg);
static void write_cb(socket_t sock, void* arg);
static void error_cb(socket_t sock, void* arg);
static void resume_read_cb(struct ll_queue* q, void* arg);
static void fin_deferred_cb(struct ll_queue* q, void* arg);
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
static void flush_write_buf(struct tcp_conn* tc);
static const uint8_t tcp_fin_sentinel;
struct tcp_conn* tcp_conn_create(
struct UASYNC* ua, socket_t sock,
size_t entry_data_size, size_t write_chunk_size,
int read_high_water, int read_low_water,
void (*on_fin)(struct tcp_conn* tc, void* arg),
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg)
{
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
tc->ua = ua;
tc->sock = sock;
tc->entry_data_size = entry_data_size;
tc->write_chunk_size = write_chunk_size;
tc->read_high_water = read_high_water;
tc->read_low_water = read_low_water;
tc->on_fin = on_fin;
tc->on_error = on_error;
tc->arg = arg;
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry));
{
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
tc->data_pool = memory_pool_init(ds);
}
if (!tc->entry_pool || !tc->data_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: memory_pool_init failed");
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
u_free(tc); return NULL;
}
tc->read_queue = queue_new(ua, 0, 0, 0, "tcp_io_rq");
tc->write_queue = queue_new(ua, 0, 0, 0, "tcp_io_wq");
if (!tc->read_queue || !tc->write_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: queue_new failed");
if (tc->read_queue) queue_free(tc->read_queue);
if (tc->write_queue) queue_free(tc->write_queue);
memory_pool_destroy(tc->entry_pool);
memory_pool_destroy(tc->data_pool);
u_free(tc); return NULL;
}
queue_set_threshold(tc->read_queue, read_low_water, 0);
queue_set_threshold(tc->write_queue, 32, 0);
queue_set_callback(tc->write_queue, write_queue_fetch_cb, tc);
queue_set_waiter_defer(tc->write_queue, 1);
tc->socket_id = uasync_add_socket_t(ua, sock, read_cb, write_cb, error_cb, tc);
if (!tc->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: uasync_add_socket_t failed");
queue_free(tc->read_queue);
queue_free(tc->write_queue);
memory_pool_destroy(tc->entry_pool);
memory_pool_destroy(tc->data_pool);
u_free(tc); return NULL;
}
tc->write_monitor = 1;
{
struct sockaddr_storage addr;
socklen_t alen = sizeof(addr);
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d connected=%d",
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, tc->connected);
return tc;
}
void tcp_conn_destroy(struct tcp_conn* tc) {
if (!tc) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
if (tc->socket_id) {
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
}
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
queue_set_empty_callback(tc->read_queue, NULL, NULL);
queue_set_callback(tc->read_queue, NULL, NULL);
queue_set_callback(tc->write_queue, NULL, NULL);
struct ll_entry* e;
while ((e = queue_data_get(tc->read_queue)) != NULL) {
if (e->dgram) memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e); queue_resume_callback(tc->read_queue);
}
while ((e = queue_data_get(tc->write_queue)) != NULL) {
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e); queue_resume_callback(tc->write_queue);
}
queue_free(tc->read_queue);
queue_free(tc->write_queue);
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
memory_pool_destroy(tc->entry_pool);
memory_pool_destroy(tc->data_pool);
u_free(tc);
}
// ====================================================================
// Чтение из сокета
// ====================================================================
static void read_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
uint8_t* buf = memory_pool_alloc(tc->data_pool);
if (!e || !buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: read_cb alloc failed fd=%d", (int)tc->sock);
if (e) queue_entry_free(e);
if (buf) memory_pool_free(tc->data_pool, buf);
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
tc->read_paused = 1;
return;
}
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
if (n > 0) {
e->dgram = buf;
e->len = (uint16_t)n;
queue_data_put(tc->read_queue, e);
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
tc->read_paused = 1;
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
}
} else if (n == 0) {
memory_pool_free(tc->data_pool, buf);
queue_entry_free(e);
tc->fin_remote = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
if (tc->read_queue->count == 0) {
if (tc->on_fin) tc->on_fin(tc, tc->arg);
} else {
queue_set_empty_callback(tc->read_queue, fin_deferred_cb, tc);
}
} else {
memory_pool_free(tc->data_pool, buf);
queue_entry_free(e);
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
tc->error = 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
}
}
static void resume_read_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
uasync_set_socket_read(tc->ua, tc->socket_id, 1);
tc->read_paused = 0;
}
static void fin_deferred_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (tc->on_fin) tc->on_fin(tc, tc->arg);
}
// ====================================================================
// Запись в сокет
// ====================================================================
static void flush_write_buf(struct tcp_conn* tc) {
while (tc->write_buf && tc->write_offset < tc->write_len) {
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
if (n > 0) {
tc->write_offset += (size_t)n;
if (tc->write_offset >= tc->write_len) {
memory_pool_free(tc->data_pool, tc->write_buf);
tc->write_buf = NULL;
tc->write_len = 0;
tc->write_offset = 0;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
return;
}
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return;
tc->error = 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
return;
}
}
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
if (tc->write_buf) {
flush_write_buf(tc);
if (tc->write_buf) return; // остался остаток, ждём write_cb
}
if (!tc->connected) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d not connected", (int)tc->sock); return; }
struct ll_entry* e = queue_data_get(q);
if (!e) {
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
tc->write_monitor = 0;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT OFF fd=%d (queue empty)", (int)tc->sock);
queue_resume_callback(q);
if (tc->on_flushed) {
void (*cb)(struct tcp_conn*, void*) = tc->on_flushed;
tc->on_flushed = NULL;
cb(tc, tc->arg);
}
return;
}
if (e->len == 0) {
if (!e->dgram) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
uasync_remove_socket_t(tc->ua, tc->sock);
socket_close_wrapper(tc->sock);
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
if (tc->on_closed) tc->on_closed(tc, tc->arg);
} else if (e->dgram == &tcp_fin_sentinel) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
shutdown(tc->sock, SHUT_WR);
tc->fin_local = 1;
if (tc->on_fin_sent) tc->on_fin_sent(tc, tc->arg);
} else {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch zero-len entry with unknown dgram=%p fd=%d", (void*)e->dgram, (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
}
return;
}
ssize_t n = send(tc->sock, e->dgram, e->len, MSG_NOSIGNAL);
if (n == (ssize_t)e->len) {
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
queue_resume_callback(q);
} else if (n > 0) {
size_t rem = e->len - (size_t)n;
tc->write_buf = memory_pool_alloc(tc->data_pool);
if (!tc->write_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
tc->error = 1;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
return;
}
memcpy(tc->write_buf, e->dgram + n, rem);
tc->write_len = rem;
tc->write_offset = 0;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
tc->write_monitor = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (partial send, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
} else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
tc->write_buf = memory_pool_alloc(tc->data_pool);
if (!tc->write_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
tc->error = 1;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
return;
}
memcpy(tc->write_buf, e->dgram, e->len);
tc->write_len = e->len;
tc->write_offset = 0;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
tc->write_monitor = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
} else {
tc->error = 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
}
}
static void write_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
if (!tc->connected) {
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
tc->connected = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
} else {
tc->error = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb connect fail fd=%d err=%d wmon=%d (POLLOUT still active)", (int)tc->sock, err, tc->write_monitor);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
if (tc->on_error) tc->on_error(tc, err ? err : -1, tc->arg);
return;
}
}
flush_write_buf(tc);
if (!tc->write_buf) {
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
queue_resume_callback(tc->write_queue);
}
}
// ====================================================================
// Асинхронная ошибка сокета
// ====================================================================
static void error_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
tc->error = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: error_cb fd=%d connected=%d fin_remote=%d wmon=%d", (int)tc->sock, tc->connected, tc->fin_remote, tc->write_monitor);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d", (int)tc->sock);
if (tc->on_error) tc->on_error(tc, -1, tc->arg);
}
// ====================================================================
// FIN / Close через очередь отправки
// ====================================================================
int tcp_conn_push_fin(struct tcp_conn* tc) {
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin invalid tc"); return -1; }
if (tc->fin_local) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already fin_local fd=%d", (int)tc->sock); return -1; }
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already closed fd=%d", (int)tc->sock); return -1; }
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
queue_data_put(tc->write_queue, e);
return 0;
}
int tcp_conn_push_close(struct tcp_conn* tc) {
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close invalid tc"); return -1; }
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close already closed fd=%d", (int)tc->sock); return -1; }
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
e->dgram = NULL; e->len = 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
queue_data_put(tc->write_queue, e);
return 0;
}
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg)) {
if (!tc) return;
tc->on_flushed = on_flushed;
}
void tcp_conn_pause_read(struct tcp_conn* tc) {
if (!tc || tc->sock == SOCKET_INVALID) return;
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
tc->read_paused = 1;
}
+125
View File
@@ -0,0 +1,125 @@
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
//
// Один tcp_conn = одно TCP-соединение. Две очереди: read_queue (сокет → данные) и
// write_queue (данные → сокет). Обе работают через автозабор ll_queue (deferred).
//
// Пул-аллокация:
// entry_pool — только struct ll_entry (без inline data)
// data_pool — буферы данных чтения/записи (max(entry_data_size, write_chunk_size))
// Данные и структуры аллоцируются раздельно — не копируются при recv/send.
//
// Чтение:
// read_cb → memory_pool_alloc(read_pool) → recv() прямо в буфер → entry в read_queue
// read_queue автозабор(deferred) → внешний коллбэк (например, отправка в ETCP)
// high_water → пауза чтения (убираем EPOLLIN), low_water/waiter → возобновление
// FIN: если read_queue не пуст → empty_callback откладывает on_fin; иначе сразу
//
// Запись:
// Внешний код: tcp_conn_push_write(data, len) → аллокация через write_pool → entry в write_queue
// write_queue автозабор(deferred): fetch_cb → send() в сокет
// EAGAIN → write_buf (из data_pool) + EPOLLOUT ON → write_cb досылает → resume автозабора
// Всё отправлено → EPOLLOUT OFF + on_flushed (если установлен)
//
// FIN / Close через очередь отправки:
// tcp_conn_push_fin(tc) — ставит FIN в write_queue (dgram=&sentinel, len=0)
// tcp_conn_push_close(tc) — ставит CLOSE в write_queue (dgram=NULL, len=0)
// Оба сигнала обрабатываются после всех предшествующих данных в очереди:
// FIN → shutdown(SHUT_WR), стоп чтения, on_fin_sent
// CLOSE → close сокета, on_closed
// После FIN входящие данные отбрасываются (read_cb → discard).
// После CLOSE сокет закрыт, tc->closed=1, дальнейшая отправка невозможна.
//
// Connect:
// tcp_conn_create регистрирует сокет с read_cb + write_cb в uasync
// После create вызывается connect() (неблокирующий, EINPROGRESS)
// write_cb детектит завершение connect через getsockopt(SO_ERROR)
// getpeername() сразу после create проверяет pre-connected сокеты (socketpair)
// До connect данные копятся в write_queue (автозабор не пытается send на unconnected сокет)
#ifndef TCP_IO_H
#define TCP_IO_H
#include "u_async.h"
#include "socket_compat.h"
#include "ll_queue.h"
#include "memory_pool.h"
struct tcp_conn {
socket_t sock;
struct UASYNC* ua;
void* socket_id;
struct ll_queue* read_queue; // сокет → данные (блоки до entry_data_size)
struct ll_queue* write_queue; // данные → сокет (блоки до write_chunk_size)
int read_high_water;
int read_low_water;
uint8_t read_paused;
uint8_t write_monitor; // 1 = EPOLLOUT активен
uint8_t connected;
uint8_t error;
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
uint8_t closed; // сокет полностью закрыт (close)
// Частичная отправка (из write_pool, не в очереди — досылается первой)
uint8_t* write_buf;
size_t write_len;
size_t write_offset;
size_t write_chunk_size;
// Пулы памяти
struct memory_pool* entry_pool; // sizeof(struct ll_entry)
struct memory_pool* data_pool; // max(entry_data_size, write_chunk_size)
size_t entry_data_size;
struct queue_waiter_handle read_waiter;
// Коллбэки
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // ошибка сокета
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
void* arg;
};
struct tcp_conn* tcp_conn_create(
struct UASYNC* ua,
socket_t sock,
size_t entry_data_size,
size_t write_chunk_size,
int read_high_water,
int read_low_water,
void (*on_fin)(struct tcp_conn* tc, void* arg),
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg);
void tcp_conn_destroy(struct tcp_conn* tc);
// Внешний код пишет данные в tc->write_queue напрямую (queue_data_put).
// Автозабор write_queue (deferred) сам отправляет когда сокет готов.
// Перед push проверять порог: queue_set_threshold в tcp_conn_create (32 entries).
// При заполнении — queue_waiter_wait на освобождение.
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
// shutdown(SHUT_WR), останавливает чтение, вызывает on_fin_sent.
// Повторный вызов игнорируется (fin_sent уже установлен).
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
// сокет (close), вызывает on_closed.
// После close сокет удалён из uasync, tc->closed=1.
// Повторный вызов игнорируется.
int tcp_conn_push_fin(struct tcp_conn* tc);
int tcp_conn_push_close(struct tcp_conn* tc);
// Одноразовый коллбэк: вызывается когда write_queue + write_buf полностью опустели.
// После вызова сбрасывается. Установить повторно можно в любой момент.
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg));
// Принудительная остановка чтения: убирает EPOLLIN и отменяет read_waiter.
void tcp_conn_pause_read(struct tcp_conn* tc);
#endif
+140 -42
View File
@@ -42,17 +42,19 @@ struct timeout_node {
};
// Socket node with array-based storage
struct socket_node {
int fd; // File descriptor (for pipe, file)
socket_t sock; // Socket (for cross-platform sockets)
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
socket_callback_t read_cbk; // For FD type
socket_callback_t write_cbk; // For FD type
socket_t_callback_t read_cbk_sock; // For SOCK type
socket_t_callback_t write_cbk_sock; // For SOCK type
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if u_freed (for reuse)
struct socket_node {
int fd; // File descriptor (for pipe, file)
socket_t sock; // Socket (for cross-platform sockets)
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
socket_callback_t read_cbk; // For FD type
socket_callback_t write_cbk; // For FD type
socket_t_callback_t read_cbk_sock; // For SOCK type
socket_t_callback_t write_cbk_sock; // For SOCK type
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if u_freed (for reuse)
int enable_read; // 1 if read monitoring is enabled
int enable_write; // 1 if write monitoring is enabled
};
// Array-based socket management for O(1) operations
@@ -187,10 +189,12 @@ static int socket_array_add_internal(struct socket_array* sa, int fd, socket_t s
sa->sockets[index].write_cbk = write_cbk_fd;
sa->sockets[index].read_cbk_sock = read_cbk_sock;
sa->sockets[index].write_cbk_sock = write_cbk_sock;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->sockets[index].enable_read = (read_cbk_fd != NULL || read_cbk_sock != NULL) ? 1 : 0;
sa->sockets[index].enable_write = (write_cbk_fd != NULL || write_cbk_sock != NULL) ? 1 : 0;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
sa->active_indices[sa->count] = index; // Add to active list
@@ -240,6 +244,8 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
sa->sockets[index].write_cbk_sock = NULL;
sa->sockets[index].except_cbk = NULL;
sa->sockets[index].user_data = NULL;
sa->sockets[index].enable_read = 0;
sa->sockets[index].enable_write = 0;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
@@ -444,6 +450,7 @@ static void process_timeouts(struct UASYNC* ua) {
}
if (node && node->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→immediate %s", node->name[0] ? node->name : "");
node->callback(node->arg);
}
@@ -469,7 +476,7 @@ static void process_timeouts(struct UASYNC* ua) {
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
// Execute callback only if not cancelled
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→%s expired", node->name[0] ? node->name : "");
node->callback(node->arg);
}
@@ -755,10 +762,84 @@ err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
ua->poll_fds_dirty = 1;
return ERR_OK;
}
return ERR_FAIL;
}
// Helper function to rebuild cached pollfd array
return ERR_FAIL;
}
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (!node->active || node->fd < 0) return ERR_FAIL;
int val = enable ? 1 : 0;
if (node->enable_read == val) return ERR_OK;
node->enable_read = val;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = 0;
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->sock;
} else {
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->fd;
}
if (node->except_cbk) ev.events |= EPOLLPRI;
#ifdef _WIN32
int efd = (int)(intptr_t)ev.data.fd;
#else
int efd = ev.data.fd;
#endif
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
}
#endif
ua->poll_fds_dirty = 1;
return ERR_OK;
}
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (!node->active || node->fd < 0) return ERR_FAIL;
int val = enable ? 1 : 0;
if (node->enable_write == val) return ERR_OK;
node->enable_write = val;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = 0;
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->sock;
} else {
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->fd;
}
if (node->except_cbk) ev.events |= EPOLLPRI;
#ifdef _WIN32
int efd = (int)(intptr_t)ev.data.fd;
#else
int efd = ev.data.fd;
#endif
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
}
#endif
ua->poll_fds_dirty = 1;
return ERR_OK;
}
// Helper function to rebuild cached pollfd array
static void rebuild_poll_fds(struct UASYNC* ua) {
if (!ua || !ua->sockets) return;
@@ -808,14 +889,14 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
ua->poll_fds[idx].events = 0;
ua->poll_fds[idx].revents = 0;
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
if (cur->read_cbk_sock) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk_sock) ua->poll_fds[idx].events |= POLLOUT;
} else {
if (cur->read_cbk) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
}
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
if (cur->read_cbk_sock && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk_sock && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
} else {
if (cur->read_cbk && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
}
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
if (cur->except_cbk) ua->poll_fds[idx].events |= POLLPRI;
idx++;
@@ -840,6 +921,8 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
// 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);
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d ev=0x%x%s", events[i].data.fd, events[i].events,
!node || !node->active ? " (inactive)" : "");
if (!node || !node->active) continue;
int local_fd = node->fd;
socket_t local_sock = node->sock;
@@ -892,7 +975,9 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
if (!ua->sockets || !ua->timeout_heap) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "poll");
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, %zu timers, timeout=%d.%dms)",
ua->sockets->count, ua->timeout_heap->size, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
// Handle negative or zero timeout
if (timeout_tb < 0) timeout_tb = -1; // Infinite wait
@@ -1012,12 +1097,12 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
s = (SOCKET)node->fd;
}
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock) FD_SET(s, &read_fds);
if (node->write_cbk_sock) FD_SET(s, &write_fds);
} else {
if (node->read_cbk) FD_SET(s, &read_fds);
if (node->write_cbk) FD_SET(s, &write_fds);
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) FD_SET(s, &read_fds);
if (node->write_cbk_sock && node->enable_write) FD_SET(s, &write_fds);
} else {
if (node->read_cbk && node->enable_read) FD_SET(s, &read_fds);
if (node->write_cbk && node->enable_write) FD_SET(s, &write_fds);
}
if (node->except_cbk) FD_SET(s, &except_fds);
@@ -1055,7 +1140,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
int has_write = FD_ISSET(s, &write_fds);
int has_except = FD_ISSET(s, &except_fds);
if (!has_read && !has_write && !has_except) continue;
if (!has_read && !has_write && !has_except) continue;
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "select→fd=%d r=%d w=%d e=%d", (int)s, has_read, has_write, has_except);
if (has_except) {
if (node->except_cbk) {
@@ -1113,7 +1199,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
/* Process socket events first to give sockets higher priority */
if (ret > 0) {
for (int i = 0; i < ua->poll_fds_count; i++) {
if (ua->poll_fds[i].revents == 0) continue;
if (ua->poll_fds[i].revents == 0) continue;
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d rev=0x%x", ua->poll_fds[i].fd, ua->poll_fds[i].revents);
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
@@ -1393,17 +1480,28 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
// Показать активные таймеры
if (ua->timeout_heap) {
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
size_t deleted_timers = 0;
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
uint64_t exp = ua->timeout_heap->heap[i].expiration;
int64_t remain = (int64_t)(exp - now_ms);
if (ua->timeout_heap->heap[i].deleted) {
deleted_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer DELETED: node=%p name='%s' exp=%llu remain=%lldms",
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
} else {
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, " Timer ACTIVE: node=%p name='%s' exp=%llu remain=%lldms",
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
}
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu", active_timers);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu, deleted: %zu, total: %zu",
active_timers, deleted_timers, ua->timeout_heap->size);
}
// Показать активные сокеты
+11 -2
View File
@@ -86,6 +86,11 @@ uint64_t get_time_tb(void);
uint64_t get_time_us(void);
// Timeouts, timebase = 0.1 mS
// Как работать с таймаутами если таймаут может быть использован несколько раз:
// 1. заводим дескриптор таймаута и обнуляем
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
// 3. при cancel или срабатывании дескриптор обнуляем
// это обеспечит отсутствие утечек и задвоений таймаутов
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);
@@ -97,8 +102,12 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
// Sockets - for socket_t (cross-platform sockets)
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, void* user_arg);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
// Динамическое управление мониторингом чтения/записи для сокета
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(struct UASYNC* ua, int timeout_tb);
+2408
View File
File diff suppressed because it is too large. Load diff
+5 -4
View File
@@ -20,6 +20,7 @@ utun_CORE_SOURCES = \
tun_windows.c \
etcp.c \
etcp_connections.c \
etcp_bbr.c \
etcp_loadbalancer.c \
etcp_debug.c \
etcp_dump.c \
@@ -33,11 +34,11 @@ utun_CORE_SOURCES = \
eim_nat.c \
nat_transport.c \
dummynet.c \
tcp_proxy.c \
proxy/tcp_proxy_client.c \
etcp_router.c \
remote_proxy.c \
udp_proxy.c \
icmp_proxy.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \
+33 -38
View File
@@ -45,8 +45,8 @@ typedef enum {
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY,
SECTION_REMOTE_PROXY
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER
} section_type_t;
static char* trim(char *str) {
@@ -376,12 +376,8 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "inflight_min_bytes") == 0) {
global->inflight_min_bytes = atoi(value);
return 0;
}
if (strcmp(key, "inflight_max_bytes") == 0) {
global->inflight_max_bytes = atoi(value);
if (strcmp(key, "bbr_max_cwnd") == 0) {
global->bbr_max_cwnd = atoi(value);
return 0;
}
if (strcmp(key, "debug_level") == 0) {
@@ -411,7 +407,7 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, 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);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1;
}
@@ -440,30 +436,30 @@ static int parse_control(const char *key, const char *value, struct global_confi
return -1;
}
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
static int parse_tcp_proxy_client(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;
global->tcp_proxy_client_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);
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_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);
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
return 0;
}
if (strcmp(key, "mtu") == 0) {
global->tcp_proxy_mtu = atoi(value);
global->tcp_proxy_client_mtu = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
global->tcp_proxy_client_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);
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
return -1;
}
char buf[256];
@@ -475,29 +471,29 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
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);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client 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; }
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client 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);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client 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; }
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client 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++;
int idx = global->tcp_proxy_client_mapping_count;
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_client_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);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
return -1;
}
@@ -694,8 +690,8 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
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;
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -724,8 +720,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.inflight_min_bytes = 2000; // Default 2KB
cfg->global.inflight_max_bytes = 100000; // Default 100KB
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
@@ -866,15 +861,15 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// 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
case SECTION_TCP_PROXY_CLIENT:
cfg->global.tcp_proxy_client_enabled = 1;
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy_client 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
case SECTION_TCP_PROXY_SERVER:
cfg->global.tcp_proxy_server_enabled = 1;
// tcp_proxy_server section has no options — its presence alone enables it
(void)key; (void)value;
break;
default:
+13 -14
View File
@@ -82,8 +82,8 @@ struct CFG_ALLOWED_KEY {
struct CFG_ALLOWED_KEY *next;
};
#define MAX_TCP_PROXY_MAPPINGS 32
struct tcp_proxy_mapping_config {
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
struct tcp_proxy_client_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
@@ -118,8 +118,7 @@ struct global_config {
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200)
int inflight_min_bytes; // inflight window starting size (default: 2000)
int inflight_max_bytes; // inflight window max cap (default: 100000)
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
// Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules;
@@ -151,17 +150,17 @@ struct global_config {
} 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;
// TCP proxy client configuration ([tcp_proxy_client] section)
int tcp_proxy_client_enabled;
char tcp_proxy_client_tun_name[16];
char tcp_proxy_client_tun_ip[64];
int tcp_proxy_client_mtu;
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
int tcp_proxy_client_mapping_count;
// Remote proxy (exit node) configuration ([remote_proxy] section)
int remote_proxy_enabled;
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
};
struct utun_config {
+91 -2
View File
@@ -93,6 +93,7 @@ static void close_client(struct control_server* server, struct control_client* c
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_debug_config(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);
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
@@ -613,7 +614,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_ACTION) {
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
@@ -696,6 +697,30 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_GET_DEBUG_CONFIG:
send_debug_config(server, client, req_seq);
break;
case ETCPMON_CMD_SET_DEBUG_CONFIG:
if (payload_size >= 2) {
uint8_t global_lvl = payload[0];
uint8_t cat_count = payload[1];
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
const uint8_t* levels = payload + 2;
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
if (levels[i] <= DEBUG_LEVEL_TRACE)
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
fflush(server->log_file);
}
}
break;
case ETCPMON_CMD_DISCONNECT:
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
@@ -1068,7 +1093,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].bandwidth = link->bandwidth;
link_info[i].nat_changes_count = link->nat_changes_count;
link_info[i].rtt_last = link->rtt_last;
link_info[i].rtt_avg10 = link->rtt_avg10;
link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
link_info[i].tt_last = link->tt_last;
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;
@@ -1078,6 +1103,25 @@ static void send_metrics(struct control_server* server, struct control_client* c
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;
/* BBR fields */
if (link->bbr) {
link_info[i].bbr_mode = link->bbr->mode;
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
} else {
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
}
link = link->next;
}
@@ -1135,6 +1179,51 @@ static void send_error(struct control_client* client, uint8_t error_code, const
u_free(buffer);
}
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
uint8_t cat_count = 0;
char names_buf[512];
names_buf[0] = '\0';
size_t off = 0;
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) {
const char* name = debug_get_category_name(i);
size_t name_len = strlen(name);
if (off + name_len + 2 > sizeof(names_buf)) break;
if (cat_count > 0) { names_buf[off++] = ','; }
memcpy(names_buf + off, name, name_len); off += name_len;
cat_count++;
}
names_buf[off] = '\0';
uint16_t names_len = (uint16_t)(off + 1);
uint16_t payload_size = 2 + names_len + cat_count;
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
uint8_t* pay = buffer + sizeof(*hdr);
pay[0] = (uint8_t)g_debug_config.level;
pay[1] = cat_count;
memcpy(pay + 2, names_buf, names_len);
for (uint8_t i = 0; i < cat_count; i++) pay[2 + names_len + i] = (uint8_t)g_debug_config.category_levels[i + 1];
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Sent RSP_DEBUG_CONFIG global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), g_debug_config.level, cat_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);
}
/* ============================================================================
* Helper Functions
* ============================================================================ */
-3
View File
@@ -316,9 +316,6 @@ static void dummynet_delay_callback(void* user_arg) {
dir->stats.queue_max = queue_count + 1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)",
dir_idx, queue_count + 1);
/* Запускаем шейпер если не активен */
if (!dir->shaper_pending) {
dir->shaper_pending = 1;
+86 -77
View File
@@ -16,6 +16,7 @@
#include <math.h> // For bandwidth calcs
#include <limits.h> // For UINT16_MAX
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
// Enable comprehensive debug output for ETCP module
#define DEBUG_CATEGORY_ETCP_DETAILED 1
@@ -198,6 +199,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->instance = instance;
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения
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
@@ -389,17 +391,18 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
clear_queue(etcp->recv_q);
clear_queue(etcp->ack_q);
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
queue_resume_callback(etcp->input_queue);
queue_resume_callback(etcp->input_send_q);
queue_resume_callback(etcp->input_wait_ack);
// В etcp_conn_reset(), после очистки очередей добавьте:
struct ETCP_LINK* l = etcp->links;
while (l) {
l->window_pkt_transmitted = 0;
l->window_retransmissions = 0;
l->win_timebase = 50000;
l->win_ptr = 0;
memset(l->stat_win, 0, sizeof(l->stat_win));
start_stats_timer(l); // перезапускаем с дефолтным интервалом
l->inflight_bytes = 0;
l->inflight_packets = 0;
l->delivered_bytes = 0;
l->last_ack_time_tb = get_time_tb();
l = l->next;
}
@@ -614,6 +617,16 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
}
}
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
if (!etcp) return;
uint32_t sum = 0;
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
etcp->optimal_inflight = sum;
input_queue_try_resume(etcp);
}
void etcp_stats(struct ETCP_CONN* etcp) {
if (!etcp) return;
@@ -754,8 +767,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, "etcp_retrans");
DEBUG_TRACE(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");
return;
}
current = etcp->input_wait_ack->head;
@@ -781,8 +794,8 @@ static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_
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);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q");
queue_resume_callback(etcp->input_send_q);
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy");
}
/*
@@ -840,14 +853,12 @@ 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;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
queue_resume_callback(etcp->input_send_q);
// queue_resume_callback(etcp->ack_q);
if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) {
ack_response_timer_cb(etcp);
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
}
// Process packets in send queue and transmit them
@@ -923,17 +934,23 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// Always subtract from previous last_link (if any) – this handles retransmission
if (inf_pkt->last_link) {
inf_pkt->last_link->window_retransmissions++;
inf_pkt->last_link->total_retransmissions++;
inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len;
bbr_note_loss(inf_pkt->last_link->bbr);
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);
if (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
loadbalancer_link_ready(inf_pkt->last_link);
}
// Always add to the CURRENT link (first send or retransmission)
link->inflight_bytes += inf_pkt->ll.len;
link->inflight_packets++;
link->window_pkt_transmitted++;
// BBR: сохраняем снэпшот на момент отправки
inf_pkt->delivered_at_send = link->delivered_bytes;
inf_pkt->inflight_at_send = link->inflight_bytes;
inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0;
// Update last_link for future ACK/retrans
inf_pkt->last_link = link;
@@ -1161,11 +1178,11 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move: ETCP -> PN");
// Add to output_queue using the same ETCP_FRAGMENT structure
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
next_expected_id, etcp->output_queue->count);
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
delivered_bytes += rx_pkt->ll.len;
delivered_count++;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moved packet id=%u to output_queue (qlen=%d)", etcp->log_name,
next_expected_id, etcp->output_queue->count);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
next_expected_id);
@@ -1212,11 +1229,36 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
return;
}
// === NEW: subtract inflight from the LAST link the packet was sent on ===
// === subtract inflight from the LAST link the packet was sent on ===
if (acked_pkt->last_link) {
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len;
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);
struct ETCP_LINK* link = acked_pkt->last_link;
link->inflight_bytes -= acked_pkt->ll.len;
link->inflight_packets--;
// BBR: собираем rate_sample и вызываем bbr_main
uint64_t now_tb = get_time_tb();
uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0;
struct bbr_rate_sample rs = {
.delivered = acked_pkt->ll.len,
.interval_us = interval_us,
.rtt_us = (uint32_t)link->rtt_last * 100,
.acked_sacked = acked_pkt->ll.len,
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
.tx_in_flight = acked_pkt->inflight_at_send,
.lost = (int)link->bbr_loss_since_ack,
.is_app_limited = acked_pkt->is_app_limited,
};
link->delivered_bytes += rs.delivered;
link->last_ack_time_tb = now_tb;
uint32_t new_cwnd = link->inflight_lim_bytes;
uint32_t new_pacing = link->bbr_pacing_rate;
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
(link->inflight_bytes >= link->inflight_lim_bytes));
etcp_link_update_inflight_lim(link, new_cwnd);
link->bbr_pacing_rate = new_pacing;
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
link->bbr_loss_since_ack = 0;
}
@@ -1247,6 +1289,10 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
if (!pkt) return;
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
if (!pkt->data_len) {
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
return;
@@ -1259,7 +1305,7 @@ 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);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len);
while (len >= 1) {
uint8_t type = data[0];
@@ -1298,45 +1344,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t new_rtt=cur_ts-ret_ts;
pkt->link->rtt_last=new_rtt;
uint8_t old_idx = pkt->link->rtt_history_index;
uint16_t old_val = pkt->link->rtt_history[old_idx];
pkt->link->rtt_history[old_idx] = new_rtt;
pkt->link->rtt_history_index = (old_idx + 1) % 10;
if (pkt->link->rtt_history_count < 10) pkt->link->rtt_history_count++;
if (new_rtt >= pkt->link->rtt_max_val) {
pkt->link->rtt_max_val = new_rtt;
pkt->link->rtt_max_idx = old_idx;
} else if (old_val == pkt->link->rtt_max_val) {
pkt->link->rtt_max_idx = 255;
}
uint8_t rtt_cnt = pkt->link->rtt_history_count;
if (rtt_cnt == 1) {
pkt->link->rtt_avg10 = new_rtt;
} else {
uint32_t sum = 0;
if (pkt->link->rtt_max_idx == 255) {
uint16_t max_val = 0;
uint8_t max_pos = 0;
for (uint8_t i = 0; i < rtt_cnt; i++) {
uint16_t v = pkt->link->rtt_history[i];
sum += v;
if (v > max_val) {
max_val = v;
max_pos = i;
}
}
pkt->link->rtt_max_val = max_val;
pkt->link->rtt_max_idx = max_pos;
} else {
for (uint8_t i = 0; i < rtt_cnt; i++) {
sum += pkt->link->rtt_history[i];
}
}
pkt->link->rtt_avg10 = (sum - pkt->link->rtt_max_val) / (rtt_cnt - 1);
}
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts;
@@ -1347,29 +1354,30 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32;
pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536;
pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536;
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
data+=5; len-=5;
int d_rtt=pkt->link->rtt_avg10 - pkt->link->rtt_last;
int prev_rtt = pkt->link->rtt_last;
int d_rtt = new_rtt - prev_rtt;
if (d_rtt<0) d_rtt=-d_rtt;
pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32;
struct ETCP_LINK* c=etcp->links;
int rtt_sum=0;
int rtt_a10_max=0;
int rtt_max=0;
int tt_sum=0;
int j_sum=0;
int cnt=0;
while (c) {
rtt_sum += c->rtt_last;
if (rtt_a10_max < c->rtt_avg10) rtt_a10_max = c->rtt_avg10;
if (rtt_max < c->rtt_last) rtt_max = c->rtt_last;
tt_sum += c->tt_last;
j_sum += c->jitter/32768;
cnt++;
c=c->next;
}
etcp->rtt_last=rtt_sum/cnt;
etcp->rtt_avg_10=rtt_a10_max;
etcp->rtt_avg_10=rtt_max;
etcp->tt_last=tt_sum/cnt;
etcp->jitter=j_sum/cnt;
break;
@@ -1408,8 +1416,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
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, "etcp_ack_resp");
DEBUG_TRACE(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");
}
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;
@@ -1450,8 +1458,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
}
// 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);
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);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
} else {
etcp->rx_dup_count++;
@@ -1523,18 +1531,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
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;
float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.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);
// BBR: инициализируем верхнюю границу из burst
if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u",
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt);
}
}
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;
@@ -1557,6 +1560,12 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
}
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
}
+6
View File
@@ -72,6 +72,9 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
uint8_t retrans_req_count; // Number of retrans requests
uint8_t state; // WAIT_ACK or WAIT_SEND
uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr
uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered)
uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight)
uint8_t is_app_limited; // данные ограничены приложением на момент отправки
};
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)
@@ -227,6 +230,9 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
// Process ACK receipt - remove acknowledged packet from inflight queues
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
// Update log_name when peer_node_id becomes known
void etcp_update_log_name(struct ETCP_CONN* etcp);
+1 -1
View File
@@ -121,13 +121,13 @@ int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
int result = queue_data_put(pn->input, entry);
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");
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
return 0;
}
+2 -1
View File
@@ -25,9 +25,10 @@
#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_TCP_PROXY 0x04 // TCP proxy exit node (server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
// Forward declarations
struct ETCP_CONN;
+925
View File
@@ -0,0 +1,925 @@
#include "etcp_bbr.h"
#include "../lib/u_async.h"
#include <stdlib.h>
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
static void bbr_start_bw_probe_down(struct bbr* bbr);
static void bbr_start_bw_probe_cruise(struct bbr* bbr);
static void bbr_exit_probe_rtt(struct bbr* bbr);
static const int bbr_pacing_gain[] = {
BBR_UNIT * 5 / 4, /* PROBE_UP: 1.25 */
BBR_UNIT * 3 / 4, /* PROBE_DOWN: 0.75 */
BBR_UNIT, /* CRUISE: 1.00 */
BBR_UNIT, /* REFILL: 1.00 */
};
static uint32_t bbr_max_bw(const struct bbr* bbr)
{
return (uint32_t)(bbr->bw_hi[0] > bbr->bw_hi[1] ? bbr->bw_hi[0] : bbr->bw_hi[1]);
}
static uint32_t bbr_bw(const struct bbr* bbr)
{
uint32_t max_bw = bbr_max_bw(bbr);
return (uint32_t)(max_bw < bbr->bw_lo ? max_bw : bbr->bw_lo);
}
static int bbr_full_bw_reached(const struct bbr* bbr)
{
return bbr->full_bw_reached;
}
static uint16_t bbr_extra_acked(const struct bbr* bbr)
{
return (uint16_t)(bbr->extra_acked[0] > bbr->extra_acked[1] ?
bbr->extra_acked[0] : bbr->extra_acked[1]);
}
static uint64_t bbr_rate_bytes_per_sec(uint64_t rate, int gain, int margin, uint32_t mss)
{
rate *= mss;
rate *= (uint64_t)gain;
rate >>= BBR_SCALE;
rate *= (uint64_t)(1000000 / 100) * (uint64_t)(100 - margin);
rate >>= BW_SCALE;
if (rate < 1) rate = 1;
return rate;
}
static uint32_t bbr_bw_to_pacing_rate(uint32_t bw, int gain, uint32_t mss)
{
uint64_t rate = bbr_rate_bytes_per_sec(bw, gain, 1, mss);
return (uint32_t)rate;
}
static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
{
uint64_t w;
if (bbr->min_rtt_us == ~0U)
return (uint32_t)bbr->init_cwnd * mss;
w = (uint64_t)bw * bbr->min_rtt_us;
return (uint32_t)((((w * (uint64_t)gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT) * mss;
}
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss);
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
cwnd += 2;
return cwnd;
}
static uint32_t bbr_inflight(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
{
uint32_t inflight = bbr_bdp(bbr, bw, gain, mss);
return bbr_quantization_budget((struct bbr*)bbr, inflight, mss);
}
static uint32_t bbr_ack_aggregation_cwnd(const struct bbr* bbr, uint32_t mss)
{
uint32_t max_aggr_cwnd, aggr_cwnd = 0;
(void)mss;
max_aggr_cwnd = (uint32_t)((uint64_t)bbr_bw(bbr) * 100000U / BW_UNIT);
aggr_cwnd = (uint32_t)((uint32_t)BBR_EXTRA_ACKED_GAIN * bbr_extra_acked(bbr) >> BBR_SCALE);
aggr_cwnd = (uint32_t)(aggr_cwnd < max_aggr_cwnd ? aggr_cwnd : max_aggr_cwnd);
return aggr_cwnd;
}
static uint32_t bbr_target_inflight(const struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
uint32_t bdp = bbr_inflight(bbr, bbr_bw(bbr), BBR_UNIT, mss);
return (uint32_t)(bdp < cwnd ? bdp : cwnd);
}
static int bbr_is_probing_bandwidth(const struct bbr* bbr)
{
return (bbr->mode == BBR_STARTUP) ||
(bbr->mode == BBR_PROBE_BW &&
(bbr->cycle_idx == BBR_BW_PROBE_REFILL ||
bbr->cycle_idx == BBR_BW_PROBE_UP));
}
static int bbr_has_elapsed_in_phase(const struct bbr* bbr, uint32_t interval_us)
{
uint64_t now_tb = bbr_now(bbr);
uint64_t expire_tb = bbr->cycle_mstamp_tb + (uint64_t)(interval_us / 100U);
return (int32_t)((uint32_t)expire_tb - (uint32_t)now_tb) <= 0;
}
static uint32_t bbr_update_round_start(struct bbr* bbr, const struct bbr_rate_sample* rs)
{
uint32_t round_delivered = 0;
bbr->round_start = 0;
if (rs->interval_us > 0 && !((int32_t)(rs->prior_delivered - bbr->next_rtt_delivered) < 0)) {
round_delivered = bbr->delivered - bbr->next_rtt_delivered;
bbr->next_rtt_delivered = bbr->delivered;
bbr->round_start = 1;
}
return round_delivered;
}
static uint32_t bbr_calculate_bw_sample(const struct bbr_rate_sample* rs)
{
if (rs->interval_us > 0)
return (uint32_t)(((uint64_t)rs->delivered * BW_UNIT + rs->interval_us - 1) / rs->interval_us);
return 0;
}
static void bbr_update_latest_delivery_signals(struct bbr* bbr,
const struct bbr_rate_sample* rs, uint32_t sample_bw)
{
bbr->loss_round_start = 0;
if (rs->interval_us <= 0 || !rs->acked_sacked)
return;
bbr->bw_latest = (uint32_t)(bbr->bw_latest > sample_bw ? bbr->bw_latest : sample_bw);
bbr->inflight_latest = (uint32_t)(bbr->inflight_latest > (uint32_t)rs->delivered ?
bbr->inflight_latest : (uint32_t)rs->delivered);
if (!((int32_t)(rs->prior_delivered - bbr->loss_round_delivered) < 0)) {
bbr->loss_round_delivered = bbr->delivered;
bbr->loss_round_start = 1;
}
}
static void bbr_advance_latest_delivery_signals(struct bbr* bbr,
const struct bbr_rate_sample* rs, uint32_t sample_bw)
{
if (bbr->loss_round_start) {
bbr->bw_latest = sample_bw;
bbr->inflight_latest = (uint32_t)rs->delivered;
}
}
static void bbr_take_max_bw_sample(struct bbr* bbr, uint32_t bw)
{
bbr->bw_hi[1] = (uint32_t)(bw > bbr->bw_hi[1] ? bw : bbr->bw_hi[1]);
}
static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
{
if (init_bw && bbr->bw_lo == ~0U)
bbr->bw_lo = bbr_max_bw(bbr);
if (bbr->inflight_lo == ~0U)
bbr->inflight_lo = cwnd;
if (bbr->inflight_hi == ~0U)
bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd);
}
static void bbr_loss_lower_bounds(struct bbr* bbr)
{
uint32_t loss_cut = BBR_UNIT - BBR_BETA;
bbr->bw_lo = (uint32_t)(bbr->bw_latest >
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE) ?
bbr->bw_latest :
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE));
bbr->inflight_lo = (uint32_t)(bbr->inflight_latest >
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE) ?
bbr->inflight_latest :
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE));
}
static void bbr_adapt_lower_bounds(struct bbr* bbr, uint32_t cwnd)
{
if (bbr_is_probing_bandwidth(bbr))
return;
if (bbr->loss_in_round) {
bbr_init_lower_bounds(bbr, 1, cwnd);
bbr_loss_lower_bounds(bbr);
}
bbr->bw_lo = (uint32_t)(bbr->bw_lo > 1U ? bbr->bw_lo : 1U);
}
static void bbr_reset_lower_bounds(struct bbr* bbr)
{
bbr->bw_lo = ~0U;
bbr->inflight_lo = ~0U;
}
static void bbr_reset_congestion_signals(struct bbr* bbr)
{
bbr->loss_in_round = 0;
bbr->loss_in_cycle = 0;
bbr->bw_latest = 0;
bbr->inflight_latest = 0;
}
static int bbr_is_inflight_too_high(const struct bbr* bbr, const struct bbr_rate_sample* rs)
{
uint32_t loss_thresh;
(void)bbr;
if (rs->lost > 0 && rs->tx_in_flight) {
loss_thresh = (uint32_t)((uint64_t)rs->tx_in_flight * BBR_LOSS_THRESH >> BBR_SCALE);
if ((uint32_t)rs->lost > loss_thresh)
return 1;
}
return 0;
}
static void bbr_handle_queue_too_high_in_startup(struct bbr* bbr, uint32_t mss)
{
uint32_t bdp;
(void)mss;
bbr->full_bw_reached = 1;
bdp = bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss);
bbr->inflight_hi = (uint32_t)(bdp > bbr->inflight_latest ? bdp : bbr->inflight_latest);
}
static void bbr_check_loss_too_high_in_startup(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t mss)
{
if (bbr->full_bw_reached)
return;
if (rs->lost > 0 && bbr->loss_events_in_round < 0xf)
bbr->loss_events_in_round++;
if (bbr->loss_round_start &&
bbr->loss_events_in_round >= BBR_FULL_LOSS_CNT &&
bbr_is_inflight_too_high(bbr, rs)) {
bbr_handle_queue_too_high_in_startup(bbr, mss);
return;
}
if (bbr->loss_round_start)
bbr->loss_events_in_round = 0;
}
static void bbr_reset_full_bw(struct bbr* bbr)
{
bbr->full_bw = 0;
bbr->full_bw_cnt = 0;
bbr->full_bw_now = 0;
}
static void bbr_advance_max_bw_filter(struct bbr* bbr)
{
if (!bbr->bw_hi[1])
return;
bbr->bw_hi[0] = bbr->bw_hi[1];
bbr->bw_hi[1] = 0;
}
static void bbr_check_full_bw_reached(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw)
{
uint32_t bw_thresh;
if (bbr->full_bw_now || rs->is_app_limited)
return;
bw_thresh = (uint32_t)((uint64_t)bbr->full_bw * BBR_FULL_BW_THRESH >> BBR_SCALE);
if (sample_bw >= bw_thresh) {
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
return;
}
if (!bbr->round_start)
return;
bbr->full_bw_cnt = (uint8_t)(bbr->full_bw_cnt + 1);
bbr->full_bw_now = (uint8_t)(bbr->full_bw_cnt >= BBR_FULL_BW_CNT ? 1 : 0);
bbr->full_bw_reached |= bbr->full_bw_now;
}
static void bbr_raise_inflight_hi_slope(struct bbr* bbr, uint32_t cwnd)
{
uint32_t growth_this_round = 1U << bbr->bw_probe_up_rounds;
uint32_t cnt;
bbr->bw_probe_up_rounds = (uint8_t)(bbr->bw_probe_up_rounds + 1 < 30 ?
bbr->bw_probe_up_rounds + 1 : 30);
cnt = cwnd / growth_this_round;
cnt = (uint32_t)(cnt > 1U ? cnt : 1U);
bbr->bw_probe_up_cnt = cnt;
}
static void bbr_set_cycle_idx(struct bbr* bbr, int cycle_idx)
{
bbr->cycle_idx = (uint8_t)cycle_idx;
bbr->try_fast_path = 0;
}
static void bbr_start_bw_probe_refill(struct bbr* bbr, uint32_t bw_probe_up_rounds)
{
bbr_reset_lower_bounds(bbr);
bbr->bw_probe_up_rounds = (uint8_t)bw_probe_up_rounds;
bbr->bw_probe_up_acks = 0;
bbr->stopped_risky_probe = 0;
bbr->ack_phase = BBR_ACKS_REFILLING;
bbr->next_rtt_delivered = bbr->delivered;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_REFILL);
}
static void bbr_start_bw_probe_up(struct bbr* bbr, uint32_t sample_bw, uint32_t cwnd)
{
bbr->ack_phase = BBR_ACKS_PROBE_STARTING;
bbr->next_rtt_delivered = bbr->delivered;
bbr->cycle_mstamp_tb = bbr_now(bbr);
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_UP);
bbr_raise_inflight_hi_slope(bbr, cwnd);
}
static void bbr_save_cwnd(struct bbr* bbr, uint32_t cwnd)
{
if (bbr->mode != BBR_PROBE_RTT)
bbr->prior_cwnd = cwnd;
else
bbr->prior_cwnd = (uint32_t)(bbr->prior_cwnd > cwnd ? bbr->prior_cwnd : cwnd);
}
static void bbr_exit_probe_rtt(struct bbr* bbr)
{
bbr_reset_lower_bounds(bbr);
if (bbr->full_bw_reached) {
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
bbr_start_bw_probe_cruise(bbr);
} else {
bbr->mode = BBR_STARTUP;
}
}
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss)
{
(void)bbr;
return BBR_CWND_MIN_TARGET * mss;
}
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
{
uint32_t headroom;
if (bbr->inflight_hi == ~0U)
return ~0U;
headroom = (uint32_t)((uint64_t)bbr->inflight_hi * BBR_INFLIGHT_HEADROOM >> BBR_SCALE);
headroom = (uint32_t)(headroom > 1U ? headroom : 1U);
return bbr->inflight_hi - headroom;
}
static void bbr_pick_probe_wait(struct bbr* bbr)
{
bbr->rounds_since_probe = (uint8_t)((uint32_t)rand() % BBR_BW_PROBE_RAND_ROUNDS);
bbr->probe_wait_us = BBR_BW_PROBE_BASE_US + (uint32_t)rand() % BBR_BW_PROBE_RAND_US;
}
static void bbr_start_bw_probe_down(struct bbr* bbr)
{
bbr_reset_congestion_signals(bbr);
bbr->bw_probe_up_cnt = ~0U;
bbr_pick_probe_wait(bbr);
bbr->cycle_mstamp_tb = bbr_now(bbr);
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
bbr->next_rtt_delivered = bbr->delivered;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_DOWN);
}
static void bbr_start_bw_probe_cruise(struct bbr* bbr)
{
if (bbr->inflight_lo != ~0U)
bbr->inflight_lo = (uint32_t)(bbr->inflight_lo < bbr->inflight_hi ?
bbr->inflight_lo : bbr->inflight_hi);
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_CRUISE);
}
static void bbr_handle_inflight_too_high(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
const uint32_t beta = BBR_BETA;
bbr->prev_probe_too_high = 1;
bbr->bw_probe_samples = 0;
if (!rs->is_app_limited) {
uint32_t ti = bbr_target_inflight(bbr, cwnd, mss);
bbr->inflight_hi = (uint32_t)(rs->tx_in_flight >
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE) ?
rs->tx_in_flight :
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE));
}
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
bbr_start_bw_probe_down(bbr);
}
static void bbr_probe_inflight_hi_upward(struct bbr* bbr, const struct bbr_rate_sample* rs,
int is_cwnd_limited, uint32_t cwnd)
{
uint32_t delta;
if (!is_cwnd_limited || cwnd < bbr->inflight_hi)
return;
bbr->bw_probe_up_acks += rs->acked_sacked;
if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt &&
bbr->bw_probe_up_cnt > 0) {
delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt;
bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt;
bbr->inflight_hi += delta;
bbr->try_fast_path = 0;
}
if (bbr->round_start)
bbr_raise_inflight_hi_slope(bbr, cwnd);
}
static void bbr_adapt_upper_bounds(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, int is_cwnd_limited,
uint32_t cwnd, uint32_t mss, int* decided)
{
(void)sample_bw;
*decided = 0;
if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start)
bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK;
if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) {
bbr->bw_probe_samples = 0;
bbr->ack_phase = BBR_ACKS_INIT;
if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited)
bbr_advance_max_bw_filter(bbr);
if (bbr->mode == BBR_PROBE_BW && bbr->stopped_risky_probe && !bbr->prev_probe_too_high) {
bbr_start_bw_probe_refill(bbr, 0);
*decided = 1;
return;
}
}
if (bbr_is_inflight_too_high(bbr, rs)) {
if (bbr->bw_probe_samples)
bbr_handle_inflight_too_high(bbr, rs, cwnd, mss);
} else {
if (bbr->inflight_hi == ~0U)
return;
if (rs->tx_in_flight > bbr->inflight_hi)
bbr->inflight_hi = rs->tx_in_flight;
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
bbr_probe_inflight_hi_upward(bbr, rs, is_cwnd_limited, cwnd);
}
}
static int bbr_is_reno_coexistence_probe_time(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
uint32_t rounds = bbr_target_inflight(bbr, cwnd, mss);
if (rounds > BBR_BW_PROBE_MAX_ROUNDS)
rounds = BBR_BW_PROBE_MAX_ROUNDS;
return bbr->rounds_since_probe >= rounds;
}
static int bbr_check_time_to_probe_bw(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
(void)rs;
if (bbr_has_elapsed_in_phase(bbr, bbr->probe_wait_us) ||
bbr_is_reno_coexistence_probe_time(bbr, cwnd, mss)) {
bbr_start_bw_probe_refill(bbr, 0);
return 1;
}
return 0;
}
static int bbr_check_time_to_cruise(const struct bbr* bbr, uint32_t inflight,
uint32_t bw, uint32_t mss)
{
if (inflight > bbr_inflight_with_headroom(bbr))
return 0;
return inflight <= bbr_inflight(bbr, bw, BBR_UNIT, mss);
}
static void bbr_update_cycle_phase(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t mss,
uint32_t inflight_packets, int is_cwnd_limited,
uint32_t cwnd)
{
int decided = 0;
int is_bw_probe_done;
uint32_t inflight, bw;
if (!bbr->full_bw_reached)
return;
bbr_adapt_upper_bounds(bbr, rs, sample_bw, is_cwnd_limited, cwnd, mss, &decided);
if (decided)
return;
if (bbr->mode != BBR_PROBE_BW)
return;
inflight = inflight_packets;
bw = bbr_max_bw(bbr);
switch (bbr->cycle_idx) {
case BBR_BW_PROBE_CRUISE:
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
return;
break;
case BBR_BW_PROBE_REFILL:
if (bbr->round_start) {
bbr->bw_probe_samples = 1;
bbr_start_bw_probe_up(bbr, sample_bw, cwnd);
}
break;
case BBR_BW_PROBE_UP:
is_bw_probe_done = 0;
if (bbr->prev_probe_too_high && inflight >= bbr->inflight_hi) {
bbr->stopped_risky_probe = 1;
is_bw_probe_done = 1;
} else {
if (is_cwnd_limited && cwnd >= bbr->inflight_hi) {
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
} else if (bbr->full_bw_now) {
is_bw_probe_done = 1;
}
}
if (is_bw_probe_done) {
bbr->prev_probe_too_high = 0;
bbr_start_bw_probe_down(bbr);
}
break;
case BBR_BW_PROBE_DOWN:
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
return;
if (bbr_check_time_to_cruise(bbr, inflight, bw, mss))
bbr_start_bw_probe_cruise(bbr);
break;
default:
break;
}
}
static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t mss, uint32_t inflight_packets)
{
(void)rs; (void)sample_bw;
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
bbr->mode = BBR_DRAIN;
bbr_reset_congestion_signals(bbr);
}
if (bbr->mode == BBR_DRAIN &&
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
}
}
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
{
if (!bbr->probe_rtt_done_stamp_tb)
return;
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
return;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
bbr_exit_probe_rtt(bbr);
}
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd, uint32_t mss, uint32_t inflight_packets)
{
uint32_t expire;
int probe_rtt_expired, min_rtt_expired;
(void)mss;
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
if (rs->rtt_us > 0 &&
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
bbr->probe_rtt_min_us = rs->rtt_us;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
bbr->has_seen_rtt = 1;
}
if (bbr->probe_rtt_min_us == 0)
bbr->probe_rtt_min_us = ~0U;
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
bbr->min_rtt_us = bbr->probe_rtt_min_us;
bbr->min_rtt_stamp_tb = bbr_now(bbr);
}
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
bbr->mode = BBR_PROBE_RTT;
bbr_save_cwnd(bbr, *cwnd);
bbr->probe_rtt_done_stamp_tb = 0;
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
bbr->next_rtt_delivered = bbr->delivered;
}
if (bbr->mode == BBR_PROBE_RTT) {
if (!bbr->probe_rtt_done_stamp_tb &&
inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) {
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
bbr->probe_rtt_round_done = 0;
bbr->next_rtt_delivered = bbr->delivered;
} else if (bbr->probe_rtt_done_stamp_tb) {
if (bbr->round_start)
bbr->probe_rtt_round_done = 1;
if (bbr->probe_rtt_round_done)
bbr_check_probe_rtt_done(bbr, cwnd);
}
}
if (rs->delivered > 0)
bbr->idle_restart = 0;
}
static void bbr_reset_startup_mode(struct bbr* bbr)
{
bbr->mode = BBR_STARTUP;
}
static void bbr_update_gains(struct bbr* bbr)
{
switch (bbr->mode) {
case BBR_STARTUP:
bbr->pacing_gain = BBR_STARTUP_PACING_GAIN;
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
break;
case BBR_DRAIN:
bbr->pacing_gain = BBR_DRAIN_GAIN;
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
break;
case BBR_PROBE_BW:
bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx];
bbr->cwnd_gain = BBR_CWND_GAIN;
break;
case BBR_PROBE_RTT:
bbr->pacing_gain = BBR_UNIT;
bbr->cwnd_gain = BBR_UNIT;
break;
default:
break;
}
}
static void bbr_update_congestion_signals(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t cwnd)
{
uint32_t bw;
if (rs->interval_us <= 0 || !rs->acked_sacked)
return;
bw = sample_bw;
if (!rs->is_app_limited || bw >= bbr_max_bw(bbr))
bbr_take_max_bw_sample(bbr, bw);
bbr->loss_in_round |= (rs->lost > 0);
if (!bbr->loss_round_start)
return;
bbr_adapt_lower_bounds(bbr, cwnd);
bbr->loss_in_round = 0;
}
static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
uint32_t epoch_us, expected_acked, extra_acked;
uint32_t extra_acked_win_rtts_thresh = BBR_EXTRA_ACKED_WIN_RTTS;
uint64_t now_us;
(void)mss;
if (rs->acked_sacked <= 0 || rs->interval_us <= 0)
return;
if (bbr->round_start) {
bbr->extra_acked_win_rtts = (uint8_t)(bbr->extra_acked_win_rtts + 1 < 0x1F ?
bbr->extra_acked_win_rtts + 1 : 0x1F);
if (!bbr->full_bw_reached)
extra_acked_win_rtts_thresh = 1;
if (bbr->extra_acked_win_rtts >= extra_acked_win_rtts_thresh) {
bbr->extra_acked_win_rtts = 0;
bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1;
bbr->extra_acked[bbr->extra_acked_win_idx] = 0;
}
}
now_us = bbr_now(bbr) * 100ULL;
if (now_us > bbr->ack_epoch_mstamp_tb * 100ULL)
epoch_us = (uint32_t)(now_us - bbr->ack_epoch_mstamp_tb * 100ULL);
else
epoch_us = 0;
expected_acked = (uint32_t)((uint64_t)bbr_bw(bbr) * epoch_us / BW_UNIT);
if (bbr->ack_epoch_acked <= expected_acked ||
(bbr->ack_epoch_acked + rs->acked_sacked >= BBR_ACK_EPOCH_ACKED_RESET_THRESH)) {
bbr->ack_epoch_acked = 0;
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
expected_acked = 0;
}
bbr->ack_epoch_acked = (uint32_t)(bbr->ack_epoch_acked + rs->acked_sacked < 0xFFFFF ?
bbr->ack_epoch_acked + rs->acked_sacked : 0xFFFFF);
extra_acked = bbr->ack_epoch_acked - expected_acked;
extra_acked = (uint32_t)(extra_acked < cwnd ? extra_acked : cwnd);
if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx])
bbr->extra_acked[bbr->extra_acked_win_idx] = (uint16_t)extra_acked;
}
static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd,
uint32_t mss, uint32_t* pacing_rate_out)
{
uint64_t bw;
uint32_t rtt_us;
if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) {
rtt_us = bbr->min_rtt_us;
if (rtt_us < 1) rtt_us = 1;
} else {
rtt_us = 1000;
}
bw = (uint64_t)cwnd * BW_UNIT / rtt_us;
*pacing_rate_out = (uint32_t)bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1, mss);
}
static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain,
uint32_t mss, uint32_t* pacing_rate_out)
{
uint32_t rate = bbr_bw_to_pacing_rate(bw, gain, mss);
if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U)
bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, mss, pacing_rate_out);
if (bbr->full_bw_reached || rate > *pacing_rate_out)
*pacing_rate_out = rate;
}
static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t acked, uint32_t bw, int gain,
uint32_t mss, uint32_t* cwnd)
{
uint32_t target_cwnd = 0;
uint32_t local_cwnd = *cwnd;
(void)rs;
if (!acked)
goto done;
target_cwnd = bbr_bdp(bbr, bw, gain, mss);
target_cwnd += bbr_ack_aggregation_cwnd(bbr, mss);
target_cwnd = bbr_quantization_budget(bbr, target_cwnd, mss);
bbr->try_fast_path = 0;
if (bbr->full_bw_reached) {
local_cwnd += acked;
if (local_cwnd >= target_cwnd) {
local_cwnd = target_cwnd;
bbr->try_fast_path = 1;
}
} else if (local_cwnd < target_cwnd || local_cwnd < 2 * (uint32_t)bbr->init_cwnd * mss) {
local_cwnd += acked;
} else {
bbr->try_fast_path = 1;
}
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ?
local_cwnd : BBR_CWND_MIN_TARGET * mss);
done:
*cwnd = local_cwnd;
if (bbr->mode == BBR_PROBE_RTT) {
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss);
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
}
}
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
{
uint32_t cap = ~0U;
if (!bbr->initialized)
return;
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx != BBR_BW_PROBE_CRUISE)
cap = bbr->inflight_hi;
else if (bbr->mode == BBR_PROBE_RTT ||
(bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_CRUISE))
cap = bbr_inflight_with_headroom(bbr);
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss);
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
}
static int bbr_run_fast_path(struct bbr* bbr, int* update_model,
const struct bbr_rate_sample* rs, uint32_t sample_bw,
uint32_t mss, uint32_t inflight_packets,
int is_cwnd_limited, uint32_t* cwnd)
{
uint32_t prev_min_rtt_us;
uint8_t prev_mode;
if (bbr->try_fast_path && rs->is_app_limited &&
sample_bw < bbr_max_bw(bbr) && !bbr->loss_in_round) {
prev_mode = bbr->mode;
prev_min_rtt_us = bbr->min_rtt_us;
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_packets);
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_packets,
is_cwnd_limited, *cwnd);
bbr_update_min_rtt(bbr, rs, cwnd, mss, inflight_packets);
if (bbr->mode == prev_mode &&
bbr->min_rtt_us == prev_min_rtt_us &&
bbr->try_fast_path)
return 1;
*update_model = 0;
}
return 0;
}
void bbr_init(struct bbr* bbr)
{
bbr->initialized = 1;
bbr->init_cwnd = 0;
bbr->prior_cwnd = 0;
bbr->delivered = 0;
bbr->next_rtt_delivered = 0;
bbr->probe_rtt_done_stamp_tb = 0;
bbr->probe_rtt_round_done = 0;
bbr->probe_rtt_min_us = ~0U;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
bbr->min_rtt_us = ~0U;
bbr->min_rtt_stamp_tb = bbr_now(bbr);
bbr->has_seen_rtt = 0;
bbr->round_start = 0;
bbr->idle_restart = 0;
bbr->full_bw_reached = 0;
bbr->full_bw = 0;
bbr->full_bw_cnt = 0;
bbr->full_bw_now = 0;
bbr->cycle_mstamp_tb = 0;
bbr->cycle_idx = 0;
bbr_reset_startup_mode(bbr);
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
bbr->ack_epoch_acked = 0;
bbr->extra_acked_win_rtts = 0;
bbr->extra_acked_win_idx = 0;
bbr->extra_acked[0] = 0;
bbr->extra_acked[1] = 0;
bbr->try_fast_path = 0;
bbr->loss_round_delivered = 1;
bbr->loss_round_start = 0;
bbr->loss_events_in_round = 0;
bbr_reset_congestion_signals(bbr);
bbr->bw_lo = ~0U;
bbr->bw_hi[0] = 0;
bbr->bw_hi[1] = 0;
bbr->inflight_lo = ~0U;
bbr->inflight_hi = ~0U;
bbr_reset_full_bw(bbr);
bbr->bw_probe_up_cnt = ~0U;
bbr->bw_probe_up_acks = 0;
bbr->bw_probe_up_rounds = 0;
bbr->probe_wait_us = 0;
bbr->stopped_risky_probe = 0;
bbr->ack_phase = BBR_ACKS_INIT;
bbr->rounds_since_probe = 0;
bbr->bw_probe_samples = 0;
bbr->prev_probe_too_high = 0;
}
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)
{
uint32_t cwnd = *cwnd_out;
uint32_t sample_bw, bw;
int update_model = 1;
bbr->delivered += rs->delivered;
bbr_update_round_start(bbr, rs);
if (bbr->round_start)
bbr->rounds_since_probe = (uint8_t)(bbr->rounds_since_probe + 1 < 0xFF ?
bbr->rounds_since_probe + 1 : 0xFF);
sample_bw = bbr_calculate_bw_sample(rs);
bbr_update_latest_delivery_signals(bbr, rs, sample_bw);
if (bbr_run_fast_path(bbr, &update_model, rs, sample_bw, mss,
inflight_bytes, is_cwnd_limited, &cwnd))
goto out;
if (update_model) {
bbr_update_congestion_signals(bbr, rs, sample_bw, cwnd);
bbr_update_ack_aggregation(bbr, rs, cwnd, mss);
bbr_check_loss_too_high_in_startup(bbr, rs, mss);
bbr_check_full_bw_reached(bbr, rs, sample_bw);
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_bytes);
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_bytes,
is_cwnd_limited, cwnd);
bbr_update_min_rtt(bbr, rs, &cwnd, mss, inflight_bytes);
}
bbr_update_gains(bbr);
bw = bbr_bw(bbr);
bbr_set_pacing_rate(bbr, bw, bbr->pacing_gain, mss, pacing_rate_out);
bbr_set_cwnd(bbr, rs, rs->acked_sacked, bw, bbr->cwnd_gain, mss, &cwnd);
bbr_bound_cwnd_for_inflight_model(bbr, &cwnd, mss);
out:
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
bbr->loss_in_cycle |= (rs->lost > 0);
*cwnd_out = cwnd;
}
void bbr_note_loss(struct bbr* bbr)
{
if (!bbr->loss_in_round)
bbr->loss_round_delivered = bbr->delivered;
bbr->loss_in_round = 1;
bbr->loss_in_cycle = 1;
}
void bbr_tx_start(struct bbr* bbr)
{
bbr->idle_restart = 1;
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
bbr->ack_epoch_acked = 0;
}
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include <stdint.h>
#define BBR_SCALE 8
#define BBR_UNIT (1 << BBR_SCALE)
#define BW_SCALE 24
#define BW_UNIT (1 << BW_SCALE)
#define BBR_STARTUP_PACING_GAIN (BBR_UNIT * 277 / 100 + 1)
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
#define BBR_CWND_GAIN (BBR_UNIT * 2)
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
#define BBR_CWND_MIN_TARGET 4
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
#define BBR_FULL_BW_CNT 3
#define BBR_LOSS_THRESH (BBR_UNIT * 2 / 100)
#define BBR_BETA (BBR_UNIT * 30 / 100)
#define BBR_INFLIGHT_HEADROOM (BBR_UNIT * 15 / 100)
#define BBR_EXTRA_ACKED_GAIN BBR_UNIT
#define BBR_EXTRA_ACKED_WIN_RTTS 5
#define BBR_ACK_EPOCH_ACKED_RESET_THRESH (1U << 20)
#define BBR_EXTRA_ACKED_MAX_US (100 * 1000)
#define BBR_FULL_LOSS_CNT 6
#define BBR_BW_PROBE_MAX_ROUNDS 63
#define BBR_BW_PROBE_RAND_ROUNDS 2
#define BBR_BW_PROBE_BASE_US (2 * 1000000)
#define BBR_BW_PROBE_RAND_US (1 * 1000000)
#define BBR_PROBE_RTT_WIN_MS 5000
#define BBR_MIN_RTT_WIN_SEC 10
#define BBR_PROBE_RTT_MS 200
enum bbr_mode { BBR_STARTUP = 0, BBR_DRAIN = 1, BBR_PROBE_BW = 2, BBR_PROBE_RTT = 3 };
enum bbr_bw_probe_cyc { BBR_BW_PROBE_UP = 0, BBR_BW_PROBE_DOWN = 1, BBR_BW_PROBE_CRUISE = 2, BBR_BW_PROBE_REFILL = 3 };
enum bbr_ack_phase { BBR_ACKS_INIT = 0, BBR_ACKS_REFILLING, BBR_ACKS_PROBE_STARTING, BBR_ACKS_PROBE_FEEDBACK, BBR_ACKS_PROBE_STOPPING };
struct bbr_rate_sample {
uint32_t delivered;
uint32_t interval_us;
uint32_t rtt_us;
uint32_t acked_sacked;
uint32_t prior_delivered;
uint32_t tx_in_flight;
int lost;
int is_app_limited;
};
struct bbr {
uint32_t min_rtt_us;
uint32_t bw_lo;
uint32_t bw_hi[2];
uint32_t inflight_lo;
uint32_t inflight_hi;
uint32_t bw_latest;
uint32_t inflight_latest;
uint8_t mode;
uint8_t cycle_idx;
uint16_t pacing_gain;
uint16_t cwnd_gain;
uint32_t next_rtt_delivered;
uint8_t round_start : 1;
uint8_t full_bw_reached : 1;
uint8_t loss_in_round : 1;
uint8_t loss_in_cycle : 1;
uint8_t idle_restart : 1;
uint8_t try_fast_path : 1;
uint8_t has_seen_rtt : 1;
uint8_t initialized : 1;
uint32_t loss_round_delivered;
uint32_t bw_probe_up_cnt;
uint32_t bw_probe_up_acks;
uint8_t bw_probe_up_rounds;
uint8_t bw_probe_samples : 1;
uint8_t prev_probe_too_high: 1;
uint8_t stopped_risky_probe: 1;
uint8_t rounds_since_probe;
uint32_t probe_wait_us;
uint64_t cycle_mstamp_tb;
uint32_t probe_rtt_min_us;
uint64_t probe_rtt_min_stamp_tb;
uint64_t probe_rtt_done_stamp_tb;
uint8_t probe_rtt_round_done : 1;
uint16_t extra_acked[2];
uint32_t ack_epoch_acked;
uint64_t ack_epoch_mstamp_tb;
uint8_t extra_acked_win_idx : 1;
uint8_t extra_acked_win_rtts : 5;
uint32_t prior_cwnd;
uint8_t init_cwnd : 7;
uint8_t full_bw_cnt : 2;
uint8_t ack_phase : 3;
uint8_t loss_events_in_round : 4;
uint32_t delivered;
uint32_t full_bw;
uint64_t min_rtt_stamp_tb;
uint8_t loss_round_start : 1;
uint8_t full_bw_now : 1;
uint8_t pad_unused : 6;
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
};
void bbr_init(struct bbr* bbr);
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited);
void bbr_note_loss(struct bbr* bbr);
void bbr_tx_start(struct bbr* bbr);
+95 -238
View File
@@ -39,23 +39,6 @@ static void keepalive_timer_cb(void* arg);
static void link_stats_timer_cb(void* arg);
static void burst_resp_timeout_cb(void* arg);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
if (!link) return;
uint32_t old = link->inflight_lim_bytes;
link->inflight_lim_bytes = new_lim;
if (old != new_lim && link->inflight_bytes < new_lim && link->send_blocked_inflight) {
link->send_blocked_inflight = 0;
loadbalancer_link_ready(link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_link_update_inflight_lim: unblocked link (lim %u->%u)", old, new_lim);
}
// recalc connection-level optimal_inflight
if (link->etcp) {
uint32_t sum = 0;
for (struct ETCP_LINK* l = link->etcp->links; l; l = l->next) sum += l->inflight_lim_bytes;
link->etcp->optimal_inflight = sum;
}
}
// === Burst sender functions ===
void etcp_link_burst_start(struct ETCP_LINK* link) {
@@ -84,8 +67,8 @@ void etcp_link_burst_check(struct ETCP_LINK* link) {
void etcp_link_burst_finish(struct ETCP_LINK* link) {
if (!link || !link->etcp || !link->etcp->instance) return;
link->burst_active = 0;
link->burst_resp_timer = uasync_set_timeout(link->etcp->instance->ua, BURST_RESP_TIMEOUT_TB, link, burst_resp_timeout_cb, "burst_resp");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] burst end id=%u pkt_sz=%u wait_resp", link->etcp->log_name, link->burst_id, link->burst_pkt_size);
link->burst_resp_timer = uasync_set_timeout(link->etcp->instance->ua, BURST_RESP_TIMEOUT_TB, link, burst_resp_timeout_cb, "burst_resp");
if (link->etcp->link_ready_for_send_fn) link->etcp->link_ready_for_send_fn(link->etcp);
}
@@ -150,9 +133,9 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > link->mtu - dgram->noencrypt_len) s = link->mtu - dgram->noencrypt_len;
// Cap s: final encrypted = s + 46 for REQUEST (noencrypt=72)
if (s > 1472 - 46) s = 1472 - 46; // s ≤ 1426
int s_max = (int)(link->mtu) - (int)SC_NONCE_SIZE - 3 - (int)SC_CRC32_SIZE - (int)SC_TAG_SIZE - (int)SC_PUBKEY_ENC_SIZE + (int)UDP_SC_HDR_SIZE;
if (s > s_max) s = s_max;
if (s < 0) s = 0;
int to_add=s-offset-UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;
@@ -256,15 +239,16 @@ static void etcp_link_send_keepalive(struct ETCP_LINK* link) {
}
dgram->link = link;
dgram->data_len = 0; // Empty packet - only timestamp in header
dgram->data[0] = ETCP_KEEPALIVE;
dgram->data[1] = link->ka_period_ms & 0xFF;
dgram->data[2] = link->ka_period_ms >> 8;
dgram->data_len = 3;
dgram->noencrypt_len = 0;
dgram->timestamp = get_current_timestamp();
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] Sending keepalive on link %p (local_id=%d)",
link->etcp->log_name, link, link->local_link_id);
dgram->flag_up = link->recv_keepalive;
link->keepalive_sent_count++;
etcp_encrypt_send(dgram);
u_free(dgram);
}
@@ -292,8 +276,8 @@ static void start_keepalive_timer(struct ETCP_LINK* link) {
}
if (link->keepalive_timer == NULL) {
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->keepalive_interval * 10, link, keepalive_timer_cb, "link_keepalive");
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] Keepalive timer started on link %p (interval=%d ms)", link->etcp->log_name, link, link->keepalive_interval);
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->keepalive_interval * 10, link, keepalive_timer_cb, "link_keepalive");
}
}
@@ -337,17 +321,21 @@ static void keepalive_timer_cb(void* arg) {
}
}
// Send keepalive only if no packets were sent since last tick
if (!link->pkt_sent_since_keepalive) {
if (link->is_server) {
if (link->recv_keepalive) etcp_link_send_keepalive(link);// сервер прекращает слать keepalive если линк потерян (ждём keepalive клиента)
}
else etcp_link_send_keepalive(link);
}
// Adaptive keepalive period
if (link->pkt_sent_since_keepalive)
link->ka_period_ms = KA_PERIOD_MIN_MS; // data flowing → reset to 200ms
else {
uint32_t next = (uint32_t)link->ka_period_ms * 105 / 100 + 1;
link->ka_period_ms = next > KA_PERIOD_MAX_MS ? KA_PERIOD_MAX_MS : (uint16_t)next;
}
link->pkt_sent_since_keepalive = 0;
// Send keepalive (server stops if link lost, client always sends)
if (!link->is_server || link->recv_keepalive)
etcp_link_send_keepalive(link);
restart_timer:
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->keepalive_interval * 10, link, keepalive_timer_cb, "link_keepalive");
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->ka_period_ms * 10, link, keepalive_timer_cb, "link_keepalive");
}
static uint32_t sockaddr_hash(struct sockaddr_storage* addr) {
@@ -748,9 +736,9 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
size_t i = 0;
while (i < conn->num_channels) {
struct ETCP_LINK* l = conn->links[i];
int had_conn = (l->conn != NULL);
etcp_link_close(l);
if (!had_conn) i++;
conn->links[i] = NULL; // обнулить после — etcp_link_close обращается к массиву через remove_link
i++;
}
u_free(conn->links);
@@ -786,11 +774,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->send_hook_ctx = NULL;
link->handshake_minsize = 100;
link->handshake_maxsize = mtu;// 28 = udp header size
link->rtt_swm=swm_create(1024);
if (!link->rtt_swm) {
u_free(link);
return NULL;
}
// Initialize keepalive timeout from global config
if (etcp->instance && etcp->instance->config) {
@@ -803,18 +786,25 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
if (link->keepalive_interval < 10) link->keepalive_interval = 10;
link->keepalive_sent_count = 0;
link->keepalive_recv_count = 0;
link->inflight_lim_bytes = etcp->instance->config
? (uint32_t)etcp->instance->config->global.inflight_min_bytes : 2000;
if (link->inflight_lim_bytes < link->mtu * 2) link->inflight_lim_bytes = link->mtu * 2;
link->inflight_phase = INFLIGHT_PHASE_SLOW_START;
link->slow_start_threshold = 1000000; // будет обновлён после первого burst-замера
link->last_window_update_tb = 0;
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
link->ka_period_ms = KA_PERIOD_MIN_MS;
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
link->burst_id = 0;
link->burst_active = 0;
link->burst_last_time_tb = 0;
link->burst_target_bdp = 0;
link->delivered_bytes = 0;
link->last_ack_time_tb = get_time_tb();
link->bbr_pacing_rate = 0;
link->bbr_loss_since_ack = 0;
link->bbr = u_calloc(1, sizeof(struct bbr));
if (!link->bbr) {
u_free(link);
return NULL;
}
bbr_init(link->bbr);
// Выделяем свободный local_link_id
int free_id = etcp_find_free_local_link_id(etcp);
if (free_id <= 0) {
@@ -829,27 +819,11 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->ip_port_hash = sockaddr_hash(remote_addr);
link->last_recv_local_time = get_time_tb(); // Initialize to prevent immediate timeout
// RTT sliding window initialization
link->rtt_history_index = 0;
link->rtt_history_count = 0;
link->rtt_max_val = 0;
link->rtt_max_idx = 0;
// rtt_history[] is already zeroed by calloc
// Инициализация статистики
link->win_timebase = 50000; // 50 ms в микросекундах
link->win_ptr = 0;
link->window_pkt_transmitted = 0;
link->window_retransmissions = 0;
link->total_retransmissions = 0;
memset(link->stat_win, 0, sizeof(link->stat_win));
start_stats_timer(link);
// insert_link(conn, link);
if (insert_link(conn, link) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Can not insert link to socket");
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
u_free(link);
return NULL;
}
@@ -858,10 +832,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
while (l && l->next) l=l->next;
if (l) l->next = link; else etcp->links = link;
// пересчитать connection-level optimal_inflight
{ uint32_t sum = 0;
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
etcp->optimal_inflight = sum; }
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "NEW link initialized on etcp=[%s] link=%p socket=%s id=%d is_server=%d mtu=%d", etcp->log_name, link, conn->name, link->local_link_id, link->is_server, link->mtu);
@@ -873,6 +844,23 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
return link;
}
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
if (!link || !link->etcp) return;
if (new_lim < INFLIGHT_LIM_MIN) new_lim = INFLIGHT_LIM_MIN;
if (new_lim > INFLIGHT_LIM_MAX) new_lim = INFLIGHT_LIM_MAX;
uint32_t old = link->inflight_lim_bytes;
link->inflight_lim_bytes = new_lim;
if (old != new_lim && link->inflight_bytes < new_lim && link->send_blocked_inflight) {
link->send_blocked_inflight = 0;
loadbalancer_link_ready(link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unblocked link %p (inflight_lim %u -> %u)", link->etcp->log_name, link, old, new_lim);
}
etcp_conn_on_inflight_lim_changed(link->etcp);
}
void etcp_link_close(struct ETCP_LINK* link) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
if (!link) return;
@@ -886,19 +874,14 @@ void etcp_link_close(struct ETCP_LINK* link) {
struct ETCP_LINK **pp = &link->etcp->links;
while (*pp && *pp != link) pp = &(*pp)->next;
if (*pp) *pp = link->next;
if (link->stats_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
if (link->init_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer);
if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer);
etcp_conn_on_inflight_lim_changed(link->etcp);
u_free(link);
return;
}
if (link->stats_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
link->stats_timer = NULL;
}
// Cancel init timer if active
if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
@@ -930,145 +913,11 @@ void etcp_link_close(struct ETCP_LINK* link) {
remove_link(link->conn, link->ip_port_hash);
swm_destroy(link->rtt_swm);
etcp_conn_on_inflight_lim_changed(link->etcp);
u_free(link->bbr);
u_free(link);
}
void start_stats_timer(struct ETCP_LINK* link) {
if (!link || !link->etcp || !link->etcp->instance) return;
if (link->stats_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
}
uint32_t tb = link->win_timebase / 100; // us → 0.1 ms units
if (tb < 50) tb = 50; // минимум 5 ms
if (tb > 5000) tb = 5000; // max 500 ms
link->stats_timer = uasync_set_timeout(
link->etcp->instance->ua,
tb,
link,
link_stats_timer_cb,
"link_stats"
);
}
// === Новый callback таймера ===
static void link_stats_timer_cb(void* arg) {
struct ETCP_LINK* link = (struct ETCP_LINK*)arg;
if (!link || !link->etcp || !link->etcp->instance) return;
// 1. Сохраняем снимок текущего окна
link->stat_win[link->win_ptr].rtt = link->rtt_avg10;
link->stat_win[link->win_ptr].pkt_loss = (uint16_t)link->window_retransmissions;
link->stat_win[link->win_ptr].pkt_transmitted = link->window_pkt_transmitted;
swm_add(link->rtt_swm, link->rtt_avg10);
link->rtt_min=swm_get_min(link->rtt_swm);
// === Burst check: запускаем если линк насыщен и прошло время ===
etcp_link_burst_check(link);
uint64_t now_tb = get_time_tb();
int do_update = 0;
// адаптивно: обновляем окно примерно раз в RTT
if (now_tb - link->last_window_update_tb >= link->rtt_avg10 || link->last_window_update_tb == 0) {
do_update = 1;
link->last_window_update_tb = now_tb;
}
if (do_update && link->initialized) {
int new_lim = link->inflight_lim_bytes;
uint32_t loss = link->window_retransmissions;
uint32_t sent = link->window_pkt_transmitted;
uint8_t old_phase = link->inflight_phase;
// === Phase 0: Slow Start ===
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
if (link->slow_start_threshold == 0) link->slow_start_threshold = 30000;
if (loss > 0) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // первая потеря — выход
new_lim -= new_lim / 2;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (loss: %u) lim %u", link->etcp->log_name, loss, new_lim);
} else if (link->rtt_avg10 > link->rtt_min * 15 / 10) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // RTT вырос >50%
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (RTT spike: avg=%u min=%u) lim %u", link->etcp->log_name, link->rtt_avg10, link->rtt_min, new_lim);
} else if (new_lim >= link->slow_start_threshold) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // достигли порога burst
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (threshold %u reached) lim %u", link->etcp->log_name, link->slow_start_threshold, new_lim);
} else {
new_lim *= 2; // удвоение каждый RTT
}
}
// === Phase 1: Congestion Avoidance ===
if (link->inflight_phase == INFLIGHT_PHASE_CONG_AVOIDANCE) {
if (old_phase == INFLIGHT_PHASE_SLOW_START) {
// только что вышли из slow start — не трогаем лимит в этом тике
}
// Loss-based: мультипликативное снижение
else if (loss > 0 && sent > 0) {
int loss_pct = loss * 100 / sent;
int decrease = new_lim * loss_pct / 50; // loss_pct / 50 ≈ ×2 при 1% потерь
if (decrease < link->mtu) decrease = link->mtu;
if (decrease > new_lim / 2) decrease = new_lim / 2;
new_lim -= decrease;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] cong_avoid loss=%u/%u (%d%%) decrease=%d lim %u",
link->etcp->log_name, loss, sent, loss_pct, decrease, new_lim);
}
// RTT-based: пропорциональное снижение
else if (link->rtt_avg10 > link->rtt_min * 13 / 10) {
int excess = link->rtt_avg10 - link->rtt_min * 13 / 10;
int ratio = excess * 100 / link->rtt_avg10; // 0..100
int decrease = new_lim * ratio / 200; // ratio/200 ≈ до 50%
if (decrease < link->mtu) decrease = link->mtu;
if (decrease > new_lim / 4) decrease = new_lim / 4;
new_lim -= decrease;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] cong_avoid RTT spike avg=%u min=%u decrease=%d lim %u",
link->etcp->log_name, link->rtt_avg10, link->rtt_min, decrease, new_lim);
}
// Additive increase (если нет сигналов перегрузки)
else {
new_lim += link->mtu; // +1 пакет за RTT
}
}
// Clamp
if (new_lim < (int)link->mtu * 2) new_lim = link->mtu * 2;
{
int max_lim = link->etcp->instance->config
? link->etcp->instance->config->global.inflight_max_bytes : 100000;
if (new_lim > max_lim) new_lim = max_lim;
}
// Burst target cap (если есть burst_target_bdp — не превышаем 1.1×)
if (link->burst_target_bdp > 0 && new_lim > (int)(link->burst_target_bdp * 11 / 10))
new_lim = link->burst_target_bdp * 11 / 10;
if (new_lim != (int)link->inflight_lim_bytes) etcp_link_update_inflight_lim(link, (uint32_t)new_lim);
}
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] stats window updated (win_timebase=%u us, rtt=%u, retrans=%u, transmitted=%u)",
// link->etcp->log_name, link->win_timebase, link->rtt_avg10, link->window_retransmissions, link->window_pkt_transmitted);
// 2. Переходим к следующему слоту
link->win_ptr = (link->win_ptr + 1) % 32;
// 3. Обнуляем накопители для нового окна
link->window_pkt_transmitted = 0;
link->window_retransmissions = 0;
// 4. Плавная подстройка win_timebase под rtt/2 (в микросекундах)
uint32_t target_us = (uint32_t)link->rtt_avg10 * 50ULL; // rtt_avg10 (0.1 ms) → rtt/2 в us
if (target_us < 10000) target_us = 10000; // минимум 10 ms
if (target_us > 500000) target_us = 500000; // максимум 0.5 s
link->win_timebase = (link->win_timebase * 7 + target_us) / 8;
// 5. Перезапускаем таймер с новым интервалом
start_stats_timer(link);
}
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) {
if (link && link->send_hook)
@@ -1267,6 +1116,8 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "set key failed");
return -5;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping sent nonce=%016llx timeout=%d ulen=%zu",
(unsigned long long)ctx->nonce, timeout_ms, user_data_len);
etcp_send_ping_raw(dgram, e_sock->fd, &sc, addr);
u_free(dgram);
if (instance->pending_pings == NULL) {
@@ -1277,8 +1128,6 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
last->next = ctx;
}
ctx->timeout_timer = uasync_set_timeout(instance->ua, timeout_ms * 10, ctx, ping_timeout_cbk, "ping_timeout");
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping sent nonce=%016llx timeout=%d ulen=%zu",
(unsigned long long)ctx->nonce, timeout_ms, user_data_len);
return 0;
}
@@ -1698,9 +1547,8 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
int xoffset=sizeof(*ack_repl_hdr);
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > link->mtu) s = link->mtu;
// Cap s: final encrypted = s - 26 for RESPONSE (noencrypt=0)
if (s > 1472 + 26) s = 1472 + 26; // s ≤ 1498
if (s > (int)(link->mtu)) s = (int)(link->mtu);
if (s < 0) s = 0;
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;
@@ -1709,7 +1557,6 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
for (int i=0; i<to_add; i++) pkt->data[xoffset++]=rand();// fill pad
// padding end
pkt->data_len=xoffset;
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
etcp_encrypt_send(pkt);
@@ -1742,35 +1589,40 @@ process_decrypted:
pkt->noencrypt_len=0;
pkt->link=link;
link->remote_keepalive=pkt->flag_up;
if ( link->remote_keepalive && !link->link_status && link->initialized) loadbalancer_link_ready(link);// up выставляем только если и remote=up и local=up
link->link_status=link->remote_keepalive;
link->remote_keepalive = pkt->flag_up;
/* restore recv_keepalive BEFORE computing link_status = remote && local */
if (link->recv_keepalive != 1) {
link->recv_keepalive = 1;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) status changed to UP - packet received",
link->etcp->log_name, link, link->local_link_id);
}
int was_up = link->link_status;
link->link_status = link->remote_keepalive && link->recv_keepalive;
if (link->link_status && !was_up && link->initialized)
loadbalancer_link_ready(link);
link->last_recv_local_time=get_time_tb();
link->last_recv_timestamp=pkt->timestamp;
link->last_recv_updated=1;
// Mark link as up when receiving packets
if (link->recv_keepalive != 1) {
link->recv_keepalive = 1;
// loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) status changed to UP - packet received",
link->etcp->log_name, link, link->local_link_id);
}
// Count decrypted bytes
link->total_decrypted += pkt->data_len;
// Count received keepalive packets (empty packets with no payload)
if (pkt->data_len == 0) {
link->keepalive_recv_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] Received keepalive on link %p (local_id=%d)",
link->etcp->log_name, link, link->local_link_id);
}
size_t offset = 0;
uint8_t code = pkt->data[offset++];
if (code == ETCP_KEEPALIVE) {
if (pkt->data_len >= 3) {
uint16_t peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8);
link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
}
link->keepalive_recv_count++;
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return; // KA handled, nothing more to process
}
if (code == ETCP_INIT_RESPONSE || code == ETCP_INIT_RESPONSE_NOINIT) {
if (pkt_len < 22) { errorcode = 46; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE too short: pkt_len=%zu", pkt_len); goto ec_fr; }
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
@@ -1894,8 +1746,13 @@ process_decrypted:
// log_dump("RECV decrypted:", pkt->data, pkt->data_len, link);
if (link->link_state == 3) etcp_conn_input(pkt);
else memory_pool_free(e_sock->instance->pkt_pool, pkt);
if (link->link_state == 3) {
if (memory_pool_is_freed(e_sock->instance->pkt_pool, pkt)) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
etcp_conn_input(pkt);
} else memory_pool_free(e_sock->instance->pkt_pool, pkt);
return;
ec_fr:
+19 -35
View File
@@ -5,8 +5,8 @@
#include "secure_channel.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdint.h>
#include <stddef.h>
@@ -14,6 +14,9 @@
#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 INFLIGHT_LIM_MIN 8192 // 8K
#define INFLIGHT_LIM_MAX 1048576 // 1M
#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 */
@@ -26,6 +29,12 @@
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#define ETCP_KEEPALIVE 0x08
/* Адаптивный keepalive */
#define KA_PERIOD_MIN_MS 200
#define KA_PERIOD_MAX_MS 10000
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
#pragma pack(push, 1)
@@ -121,7 +130,6 @@ struct ETCP_LINK {
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
SlidingWindowMin* rtt_swm;
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
@@ -164,24 +172,7 @@ struct ETCP_LINK {
uint32_t inflight_lim_bytes;
/* Биты блокировки отправки по причине (для корректного resume) */
// unsigned send_blocked_input_queue : 1;
// unsigned send_blocked_sack_full : 1;
// unsigned send_blocked_bandwidth : 1;
unsigned send_blocked_inflight : 1;
// unsigned send_blocked_other : 4;
// lim периодически обновляется. = (rtt_avg10 + jitter * K2[=1.0]) * K1[=1.5] * channel_bandwidth
uint32_t win_timebase; // x1 us (по умолчанию 50 000 = 50 ms, потом подстраивается под rtt/2)
uint32_t window_pkt_transmitted; // текущие накопители для следующего окна (обнуляются в таймере)
uint32_t window_retransmissions;
void* stats_timer; // Таймер для обновления окна
uint32_t win_ptr; // текущий слот в кольцевом буфере (0..31)
struct {
uint16_t rtt; // x 0.1ms (avg10 в конеце окна)
// uint16_t avg_rtt; // x 0.1ms (avg10 усредненный за период)
uint16_t pkt_loss; // штук (за период)
uint32_t pkt_transmitted; // штук (за период)
} stat_win[32]; // последние 32 замера статистики
// statistics
size_t encrypt_errors;
@@ -193,14 +184,8 @@ struct ETCP_LINK {
uint32_t total_retransmissions;
uint16_t rtt_last; // round trip (время отправки + приёма)
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
uint8_t rtt_history_index; // Current write position in history buffer
uint32_t jitter; // Current jitter [>>16] x0.1 ms
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)
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
@@ -218,6 +203,7 @@ struct ETCP_LINK {
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
uint32_t keepalive_timeout; // таймаут (ms)
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
@@ -249,11 +235,12 @@ struct ETCP_LINK {
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)
// === BBR congestion control ===
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx;
@@ -278,20 +265,17 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
// connection functions
// создает новый канал связи для etcp подключения (ETCP_CONN)
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
// find free local_link_id for connection
// scans all links in connection, marks used ids in bit array
// returns first free id (0-255) or -1 if all occupied
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);
+15 -23
View File
@@ -4,7 +4,6 @@
#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>
@@ -153,32 +152,30 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->keepalive_interval, link->keepalive_timeout);
/* timers */
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s",
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%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",
/* inflight (BBR) */
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
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);
link->send_blocked_inflight,
link->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
link->bbr_pacing_rate);
/* 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);
/* rtt (BBR) */
DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
link->rtt_last, link->jitter,
link->bbr ? link->bbr->min_rtt_us : 0,
link->bbr ? link->bbr->bw_lo : 0,
link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
link->bbr ? link->bbr->inflight_lo : 0,
link->bbr ? link->bbr->inflight_hi : 0);
/* tt/rt/bandwidth */
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
@@ -201,11 +198,6 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
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,
+1 -1
View File
@@ -164,9 +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;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduling shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
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
+235 -85
View File
@@ -64,6 +64,14 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
hdr->src_node_id = inst->node_id;
hdr->seq = seq;
hdr->svc_id = rconn->svc_id;
{
uint8_t f = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
if (!rconn->start_sent && pl_len > 0 && !(seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
f |= ROUTER_FLAG_START;
rconn->start_sent = 1;
}
hdr->flags = f;
}
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, payload, pl_len);
struct ll_entry* entry = queue_entry_new(0);
@@ -73,14 +81,15 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
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",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
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);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d → %016llx",
rconn->svc_id, seq, pl_len,
(int32_t)(rconn->tx_seq - rconn->tx_acked),
(unsigned long long)rconn->remote_node_id);
return etcp_send(conn, entry);
}
@@ -99,7 +108,7 @@ static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc
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",
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
seq_flags, (unsigned long long)dst, svc_id);
etcp_send(conn, entry);
}
@@ -110,11 +119,12 @@ static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
if (!cb) return;
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(1);
e->dgram = u_malloc(9);
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",
memcpy(e->dgram + 1, &rconn->remote_node_id, 8);
e->len = 9;
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_close_svc: svc_id=%u remote=%016llx",
rconn->svc_id, (unsigned long long)rconn->remote_node_id);
cb(NULL, e);
}
@@ -143,14 +153,17 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
}
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",
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
}
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
int sq = queue_entry_count(rconn->send_q);
if (sq > 0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_DRAIN: svc_id=%u send_q=%d inflight=%d",
rconn->svc_id, sq, (int32_t)(rconn->tx_seq - rconn->tx_acked));
while (1) {
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) break;
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
struct ll_entry* e = queue_data_get(rconn->send_q);
if (!e) { rconn->send_blocked = 0; break; }
router_send_one(rconn, e->dgram, e->len);
@@ -171,84 +184,102 @@ static void router_send_resume_cb(void* arg) {
router_drain_send_q(rconn);
}
static void router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, int force) {
if (!force && queue_entry_count(rconn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: FULL svc_id=%u count=%d — backpressure",
rconn->svc_id, queue_entry_count(rconn->send_q));
return -1;
}
struct ll_entry* qe = queue_entry_new(0);
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_enqueue_send: queue_entry_new failed"); return; }
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_enqueue_send: queue_entry_new failed"); return -1; }
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_entry_free(qe); return; }
if (!qe->dgram) { queue_entry_free(qe); return -1; }
qe->len = pl_len;
if (pl_len > 0) memcpy(qe->dgram, payload, pl_len);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: queued svc_id=%u inflight=%d send_q=%d",
rconn->svc_id, (int32_t)(rconn->tx_seq - rconn->tx_acked), queue_entry_count(rconn->send_q));
queue_data_put(rconn->send_q, qe);
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));
return 0;
}
// ====================================================================
// 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_ack_do_send(struct ETCP_ROUTER_CONN* rconn) {
router_send_ack(rconn);
rconn->last_sent_ack_seq = rconn->rx_seq;
rconn->last_ack_sent_tb = get_time_tb();
}
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
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",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "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; }
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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;
hdr->flags = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
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);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ACK_SEND: svc_id=%u rx_seq=%u → %016llx",
rconn->svc_id, rconn->rx_seq, (unsigned long long)rconn->remote_node_id);
etcp_send(conn, entry);
}
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->rx_seq == rconn->last_sent_ack_seq) return;
uint64_t now = get_time_tb();
uint64_t elapsed = now - rconn->last_ack_sent_tb;
if (elapsed >= ROUTER_ACK_INTERVAL_TB) {
router_ack_do_send(rconn);
return;
}
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB - (uint32_t)elapsed, rconn, router_ack_timer_cb, "router_ack");
}
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
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
if (rconn->rx_seq != rconn->last_sent_ack_seq) {
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
} else if (!rconn->idle_ack_timer) {
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;
}
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
}
// ====================================================================
@@ -260,19 +291,19 @@ static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* con
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);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "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; }
if (!svc_entry) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
cb(conn, svc_entry);
}
@@ -299,7 +330,7 @@ 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",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: invalid packet inst=%p len=%zu min=%u",
(void*)inst, entry->len, (unsigned)SVC_ROUTE_HDR_SIZE);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
@@ -308,6 +339,9 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_RECV: svc_id=%u seq=%u len=%zu from %016llx",
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
if (hdr->dst_node_id == inst->node_id) {
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
@@ -320,9 +354,17 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
}
if (pl_len == 0) {
// ACK-пакет: обновляем rx_acked, drain send_q, выходим
if (rconn) {
rconn->rx_acked = hdr->seq;
if ((int32_t)(hdr->seq - rconn->tx_acked) >= 0) {
rconn->tx_acked = hdr->seq;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d from %016llx",
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
queue_entry_count(rconn->send_q),
(unsigned long long)hdr->src_node_id);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
hdr->seq, rconn->tx_acked, (unsigned long long)hdr->src_node_id);
}
rconn->last_dgram_ts = get_current_timestamp();
if (rconn->send_blocked) router_drain_send_q(rconn);
}
@@ -336,6 +378,31 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
}
// Обнаружение перезапуска peer'а: новый sess_id или START-флаг
{
uint8_t incoming_sess = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
if (pl_len > 0 && (incoming_sess != rconn->peer_sess_id ||
((hdr->flags & ROUTER_FLAG_START) && rconn->rx_seq > 0))) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: peer restart sess_id=%u→%u svc_id=%u from %016llx",
rconn->peer_sess_id, incoming_sess, hdr->svc_id,
(unsigned long long)hdr->src_node_id);
etcp_router_conn_restart(inst, hdr->src_node_id, hdr->svc_id);
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
rconn->peer_sess_id = incoming_sess;
}
// RST-флаг — peer сигналит нарушение последовательности
if (hdr->flags & ROUTER_FLAG_RST) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: RST from %016llx svc_id=%u",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
// Seq-состояние есть — reorder
rconn->last_dgram_ts = get_current_timestamp();
uint32_t seq = hdr->seq;
@@ -344,7 +411,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
{
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",
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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;
@@ -354,7 +421,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// Дубликат (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);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
@@ -368,10 +435,10 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
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",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
(unsigned long long)hdr->src_node_id, rconn->svc_id);
queue_data_put_with_index(rconn->recv_q, qe);
if (seq == rconn->rx_seq)
router_try_assembly(rconn, conn);
@@ -382,12 +449,12 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// ========== Транзит ==========
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",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_router: forwarding svc_id=%u → %016llx via %s",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
etcp_send(next, entry);
}
@@ -398,21 +465,21 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// ====================================================================
int etcp_router_init(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: NULL instance"); return -1; }
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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");
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
return 0;
}
@@ -424,9 +491,9 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
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->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); }
@@ -443,43 +510,43 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
inst->router_conns = NULL;
}
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router destroyed");
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "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) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: NULL instance"); return -1; }
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "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) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "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) {
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force) {
if (!inst || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_route_send: NULL inst=%p entry=%p", (void*)inst, (void*)entry);
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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;
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "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); }
@@ -488,7 +555,7 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
if (!etcp_conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
(unsigned long long)dst_node_id, svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return -1;
@@ -497,10 +564,10 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_
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);
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
int eq_ret = router_enqueue_send(rconn, entry->dgram + 1, payload_len, force);
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
return eq_ret;
}
int ret = router_send_one(rconn, entry->dgram + 1, payload_len);
@@ -515,6 +582,84 @@ int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
return queue_entry_count(conn->normalizer->input);
}
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_wait(conn->normalizer->input, h, callback, arg);
}
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_cancel(conn->normalizer->input, h);
}
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id) {
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
if (!rconn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_restart: svc_id=%u remote=%016llx — resetting local state",
svc_id, (unsigned long long)remote_node_id);
etcp_recv_fn cb = inst->router_bindings.callbacks[svc_id];
if (cb) {
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(9);
if (e->dgram) {
e->dgram[0] = svc_id;
memcpy(e->dgram + 1, &remote_node_id, 8);
e->len = 9;
cb(NULL, e);
} else { queue_entry_free(e); }
}
}
struct ll_entry* f;
if (rconn->send_q) {
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
if (rconn->recv_q) {
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
rconn->tx_seq = 0;
rconn->rx_seq = 0;
rconn->tx_acked = 0;
rconn->last_sent_ack_seq = 0;
rconn->sess_id = (rconn->sess_id + 1) & 0x3;
rconn->peer_sess_id = 0;
rconn->start_sent = 0;
rconn->send_blocked = 0;
}
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!inst || !h) return;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
queue_waiter_wait(rconn->send_q, h, callback, arg);
}
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h) {
if (!inst || !h) return;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
queue_waiter_cancel(rconn->send_q, h);
}
// ====================================================================
// Seq-connection API
// ====================================================================
@@ -527,38 +672,43 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
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; }
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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->tx_acked = 0;
rconn->last_sent_ack_seq = 0;
rconn->last_ack_sent_tb = 0;
rconn->inst = inst;
rconn->ack_timer = NULL;
rconn->idle_ack_timer = NULL;
rconn->send_blocked = 0;
rconn->send_resume_timer = NULL;
rconn->sess_id = 0;
rconn->peer_sess_id = 0;
rconn->start_sent = 0;
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_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",
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: new conn remote=%016llx svc_id=%u",
(unsigned long long)remote_node_id, svc_id);
queue_data_put_with_index(inst->router_conns, &rconn->ll);
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",
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "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;
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
return router_enqueue_send(rconn, data, len, 0);
}
return router_send_one(rconn, data, len);
}
@@ -587,7 +737,7 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t re
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",
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed by node remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
} else {
+39 -6
View File
@@ -3,7 +3,8 @@
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [payload...]
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [payload...]
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3)
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
@@ -18,11 +19,18 @@ struct SVC_ROUTE_HDR {
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
uint8_t svc_id; // идентификатор сервиса
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
};
#pragma pack(pop)
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 22
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
#define SVC_ROUTE_MAX_BINDINGS 256
// Биты в flags
#define ROUTER_FLAG_START 0x80
#define ROUTER_FLAG_RST 0x40
#define ROUTER_SESS_ID_SHIFT 4
#define ROUTER_SESS_ID_MASK 0x30
// Состояние одного логического подключения (remote_node_id + svc_id)
struct ETCP_ROUTER_CONN {
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
@@ -32,24 +40,30 @@ struct ETCP_ROUTER_CONN {
uint32_t tx_seq; // следующий seq для отправки
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
uint32_t rx_acked; // последний отправленный ACK (= rx_seq на момент отправки)
uint32_t tx_acked; // сколько наших пакетов подтвердил remote (для inflight)
uint32_t last_sent_ack_seq; // последний отправленный ACK (= rx_seq на момент отправки)
uint64_t last_ack_sent_tb; // время отправки последнего ACK (timebase 0.1ms)
struct UTUN_INSTANCE* inst;
struct ll_queue* recv_q; // reorder очередь: hash по seq (4 байта, offset 0)
void* ack_timer; // периодический таймер (100ms)
void* ack_timer; // периодический таймер (10ms при consumer_ack)
void* idle_ack_timer; // idle таймер (дослать последний ack)
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
void* send_resume_timer; // таймер возобновления отправки
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
uint8_t sess_id; // наш session id (0-3)
uint8_t peer_sess_id; // последний sess_id от peer'а
uint8_t start_sent; // 0 = нужно отправить START в первом data
};
#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_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
// Флаги в старших битах seq (взаимоисключающие)
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
@@ -71,7 +85,7 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn c
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);
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force);
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
@@ -91,4 +105,23 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t re
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
// Backpressure: зарегистрировать/отменить waiter на normalizer->input очереди
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h);
// Backpressure: зарегистрировать/отменить waiter на send_q очереди
// h — handle из структуры сервиса, callback вызывается когда send_q.count <= threshold (128)
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h);
// Сбросить состояние роутера для конкретного peer+svc (перезапуск удалённой стороны).
// Очищает send_q/recv_q, уведомляет сервис через cb(NULL, entry) с remote_node_id.
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id);
#endif // ETCP_ROUTER_H
+36 -4
View File
@@ -103,7 +103,7 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
return NULL;
}
ctx->iss_seed = (uint16_t)(get_time_tb() & 0xFFFF);
ctx->tmr_interval_ms = TCP_TMR_INTERVAL;
ctx->tmr_interval_ms = TCP_TMR_INTERVAL / 4;
ctx->rto_min_ms = 3000;
ctx->rto_max_ms = 0;
ctx->timer = uasync_set_timeout(ua, ctx->tmr_interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr");
@@ -788,6 +788,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
prev = NULL;
pcb = ctx->tw_pcbs;
while (pcb != NULL) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_slowtmr — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_slowtmr — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
pcb_remove = 0;
if ((uint32_t)(ctx->ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
@@ -804,7 +812,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
}
pcb2 = pcb;
pcb = pcb->next;
int self_loop = (pcb == pcb2);
tcp_free(pcb2);
if (self_loop) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_slowtmr: freed pcb=%p state=TIME_WAIT port=%u, clearing tw_pcbs",
(void*)pcb2, pcb2->local_port);
ctx->tw_pcbs = NULL;
break;
}
} else {
prev = pcb;
pcb = pcb->next;
@@ -1017,12 +1032,29 @@ static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx)
inactivity = 0;
inactive = NULL;
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_kill_timewait — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_kill_timewait — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity) {
inactivity = ctx->ticks - pcb->tmr;
inactive = pcb;
}
}
if (inactive != NULL) tcp_abort(inactive);
if (inactive != NULL) {
if (inactive->next == inactive) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_kill_timewait: aborting pcb=%p, clearing tw_pcbs",
(void*)inactive);
ctx->tw_pcbs = NULL;
tcp_free(inactive);
} else {
tcp_abort(inactive);
}
}
}
static void tcp_handle_closepend(struct lwip_tcp_ctx *ctx)
@@ -1080,8 +1112,8 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->ttl = 64;
pcb->mss = INITIAL_MSS;
pcb->rto = 3000 / TCP_SLOW_INTERVAL;
pcb->sv = 3000 / TCP_SLOW_INTERVAL;
pcb->rto = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->sv = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->rtime = -1;
pcb->cwnd = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "tcp_alloc: cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
+2 -2
View File
@@ -166,7 +166,6 @@ struct tcp_pcb {
tcp_connected_fn connected;
tcp_poll_fn poll;
tcp_err_fn errf;
uint32_t keep_idle;
uint8_t persist_cnt;
uint8_t persist_backoff;
@@ -185,7 +184,7 @@ struct tcp_pcb_listen {
tcp_accept_fn accept;
};
// Context for the TCP module (one per tcp_proxy instance)
// Context for the TCP module (one per tcp_proxy_client instance)
struct lwip_tcp_ctx {
struct UASYNC *ua;
tcp_output_fn output;
@@ -247,6 +246,7 @@ err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ip, uint16_t port, tcp_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);
err_t tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx);
void tcp_abort(struct tcp_pcb *pcb);
void tcp_recved(struct tcp_pcb *pcb, uint16_t len);
+17 -1
View File
@@ -208,11 +208,27 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
if (pcb == NULL) {
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in lwip_tcp_input — HALTING", (void*)pcb);
pbuf_free(p);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in lwip_tcp_input — HALTING", (void*)pcb);
pbuf_free(p);
volatile int _halt = 1; while (_halt) {}
}
if (pcb->remote_port == sport &&
pcb->local_port == dport &&
pcb->remote_ip == src_ip &&
pcb->local_ip == dst_ip) {
tcp_timewait_input(pcb);
if (pcb->next == pcb) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in lwip_tcp_input: pcb=%p sport=%u dport=%u, aborting",
(void*)pcb, sport, dport);
tcp_abort(pcb);
} else {
tcp_timewait_input(pcb);
}
pbuf_free(p);
return;
}
+2 -1
View File
@@ -14,9 +14,10 @@
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
#define TCP_RTO_MIN_MS 3000 // ms, initial RTO
#define TCP_FIN_WAIT_TIMEOUT 20000 // ms
#define TCP_SYN_RCVD_TIMEOUT 20000 // ms
#define TCP_MSL 60000 // ms
#define TCP_MSL 60000 // ms (2*MSL = 120s)
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
+2 -2
View File
@@ -504,7 +504,7 @@ tcp_output(struct tcp_pcb *pcb)
lwip_tcp_trace_record(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd,
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd, "cwd");
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
ntohl(seg->tcphdr->seqno) - pcb->lastack, seg->len, wnd, pcb->cwnd, pcb->snd_wnd);
if (wnd == pcb->snd_wnd && pcb->unacked == NULL && pcb->persist_backoff == 0) {
pcb->persist_cnt = 0;
@@ -534,7 +534,7 @@ tcp_output(struct tcp_pcb *pcb)
lwip_tcp_trace_record(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd,
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd, "snt");
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
ntohl(seg->tcphdr->seqno), seg->len, wnd, pcb->cwnd, err);
if (err != LERR_OK) {
tcp_set_flags(pcb, TF_NAGLEMEMERR);
+2 -1
View File
@@ -3,6 +3,7 @@
#define LWIP_TCP_PRIV_H
#include "lwip_tcp.h"
#include "lwip_tcp_opts.h"
#include <string.h>
// Packed struct support
@@ -240,7 +241,7 @@ void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
#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; } } } \
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)
+2 -2
View File
@@ -47,7 +47,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
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);
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry, 0);
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);
@@ -90,7 +90,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
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);
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry, 0);
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);
+4 -2
View File
@@ -69,6 +69,8 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->recvpart = NULL;
pn->flush_timer = NULL;
pn->input_waiter_handle.internal = NULL;
return pn;
}
@@ -120,7 +122,7 @@ void pn_deinit(struct PKTNORM* pn) {
// during etcp_connection_close() when drain_and_free_fragment_queue() is called
if (pn->etcp) {
if (pn->etcp->input_queue) {
queue_cancel_wait(pn->etcp->input_queue, &pn->etcp->input_queue->waiter);
queue_waiter_cancel(pn->etcp->input_queue, &pn->input_waiter_handle);
}
if (pn->etcp->output_queue) {
queue_set_callback(pn->etcp->output_queue, NULL, NULL);
@@ -213,7 +215,7 @@ static void packer_cb(struct ll_queue* q, void* arg) {
struct PKTNORM* pn = (struct PKTNORM*)arg;
if (!pn) return;
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "input_q->pn: waiting etcp input threshold");
queue_wait_threshold(pn->etcp->input_queue, 0, 0, etcp_input_ready_cb, pn);
queue_waiter_wait(pn->etcp->input_queue, &pn->input_waiter_handle, etcp_input_ready_cb, pn);
}
// Helper to send block to ETCP as ETCP_FRAGMENT
+3
View File
@@ -36,6 +36,9 @@ struct PKTNORM {
struct ll_entry* pending; // Partial processed input entry
uint16_t pending_in_ptr; // Pointer in pending entry
// backpressure:
struct queue_waiter_handle input_waiter_handle; // waiter на etcp->input_queue
// unpacker:
struct ll_entry* recvpart; // блок ожидающий заполнение
// uint16_t recvpart_rem; // сколько байт осталось собрать (0 = ждём заголовок нового пакета)
+477
View File
@@ -0,0 +1,477 @@
## TCP Proxy Protocol — архитектура (v2, без CONNECTED)
### 1. Протокольный формат (общий для client и server)
```
┌──────┬──────┬───────────────────────┬──────────────────────┐
│svc_id│subcmd│ stream_id (4) │ data ... │
│ 1B │ 1B │ │ │
└──────┴──────┴───────────────────────┴──────────────────────┘
HDR = 6 байт
subcmd:
0x01 CONNECT client→server : dest_ip(4)+dest_port(2)
0x03 DATA ↔ bidirectional: payload
0x04 CLOSE ↔ bidirectional: (нет данных)
0x05 ERROR ↔ bidirectional: (нет данных)
```
---
### 2. Диаграмма состояний `tcp_proxy_client_conn`
```
┌──────────────┐
│ ALLOC + │ accept_cb: u_calloc, stream_id++
│ CONNECT │ send CONNECT → exit
│ отправлен │
└──────┬───────┘
│
┌───────────┴───────────┐
│ send успешен? │
└───────────┬───────────┘
Y │ N
┌────────┘ └──────┐
▼ ▼
┌────────────┐ ┌──────────────┐
│ ACTIVE │ │ FREE │
│ traffic │ └──────────────┘
└────┬──┬────┘
│ │
│ └─────────────────────────────────┐
│ │
▼ lwIP FIN (p=NULL) ▼ ERROR от lwIP/exit
┌──────────────┐ ┌──────────────┐
│ TUN_CLOSED │ │ ERROR │
│ tun_closed=1 │ │ error=1 │
│ ↓ send_close │ │ send ERROR→ │
│ после drain │ └──────┬───────┘
│ tx_buf+lwIP │ │
└──────┬───────┘ poll_cb: tcp_abort+free
│
│ recv CLOSE от exit
▼
┌───────────────────┐
│ TUN+REM CLOSED │
│ tun=1 rem=1 │
│ ждём flush буферов│
└────────┬──────────┘
│ poll_cb: pending=0, unsent=NULL, unacked=NULL
│ (или sndbuf==0)
▼
┌──────────────┐
│ CLEANUP │
│ free(pc) │
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_data в ETCP вернул -1 (normalizer полон)
│ TX_BACKPRESSURE│ не tcp_recved() → окно lwIP=0 → recv_cb остановлен
│ tx_buf != NULL │ etcp_router_waiter_register() → ждём освобождения
│ tx_waiter pend │ waiter_cb: retry send_data → tcp_recved → окно открыто
└─────────────────┘
┌─────────────────┐ send_close/send_error вернул -1
│ CLOSE_PENDING │ → retry в poll_cb
│ close_pending=1│
└─────────────────┘
```
---
### 3. Диаграмма состояний `tcp_proxy_server_conn`
```
┌───────────────────┐
│ CREATING │ recv CONNECT
│ socket() создан │ → неблокирующий connect()
│ sock = неблок. │
└────────┬──────────┘
│
┌────────────┴────────────┐
│ connect() результат? │
└────┬──────────┬──────────┘
EINPROGRESS│ │ошибка сразу
▼ ▼
┌──────────┐ ┌──────────────┐
│CONNECTING│ │ ERROR │ send ERROR→client
│ждём write│ │ FREE │
└────┬─────┘ └──────────────┘
│ write_cb: SO_ERROR==0
▼
┌─────────────┐
│ CONNECTED │ connected=1
│ flush pend │ → flush pending_buf в сокет
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
client→DATA │ │ ERROR (sock/pipe)
(sock_send) │ ▼
или pending_buf │ ┌──────────────┐
│ │ │ ERROR │ send ERROR→client
│ │ │ FREE │
│ sock EOF (n=0) │ │
│ │ └──────────────┘
│ ▼
│ ┌──────────────────┐
│ │ SOCK_CLOSED │ sock_closed=1
│ │ out_buf/pause? │ если очереди пусты → send_close
│ │ ждём drain │ иначе ждём retry_cb/waiter_cb
│ └────────┬─────────┘
│ │ очереди опустошены → send_close
│ │ если cli_closed → FREE
│ │
client→CLOSE │
│ │
▼ │
┌──────────────┐ │
│ CLI_CLOSED │ │ cli_closed=1
│ out_buf pend? │◄────────┘ если out_buf пуст → shutdown(SHUT_WR)
│ ждём flush │ иначе ждём sock_retry_cb → shutdown
└──────┬───────┘
│
└──────────────┐
▼
┌──────────────────┐
│ cli && sock │ оба закрыты + очереди пусты
│ очереди пусты │
└────────┬─────────┘
▼
┌──────────────┐
│ FREE │ conn_free(rc)
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_msg в ETCP вернул -1
│ PAUSED │ буферизуем в pause_buf, убираем read_id (пауза чтения)
│ pause_buf>0 │ etcp_router_waiter_register() → ждём освобождения
│ pause_waiter │ waiter_cb: retry send_msg → resume read_id
└─────────────────┘
┌─────────────────┐ CLOSE/ERROR не доставлен через ETCP
│ CLOSE_PENDING │ close_timer с backoff 50..5000 tb → ретрай
│ close_pending=1│
└─────────────────┘
```
---
### 4. DATA FLOW
```
══════════════════════════════════════════════════════════════════════
APP → lwIP → recv_cb → ETCP → handle → socket → DEST
APP ← lwIP ← tcp_write ← ETCP ← read_cb ← socket ← DEST
══════════════════════════════════════════════════════════════════════
CLIENT (узел A) SERVER (узел B)
APP (через TUN) DEST (реальный)
│ │
▼ TCP SYN/ACK (lwIP) ▼ TCP SYN/ACK (OS)
┌──────────┐ ┌──────────┐
│ lwIP │ │ socket │
│ TCP │ │ (неблок) │
└────┬─────┘ └────┬─────┘
│ recv_cb │ read_cb
│ │
┌────▼────────────┐ ┌────▼───────────────┐
│ tcp_proxy │ ETCP │ tcp_proxy │
│ _client │◄═══════════════════════►│ _server │
│ │ │ │
│ ┌────────────┐ │ │ ┌──────────────┐ │
│ │ recv_cb │ │──CONNECT+DATA──► │ │handle_connect│ │
│ │(lwIP→ETCP) │ │ │ │handle_data │ │
│ │ │ │ backpressure: │ └──────┬───────┘ │
│ │ send fail: │ │ waiter на normalizer │ │ sock_send │
│ │ tx_buf+ │ │ │ ┌──────▼───────┐ │
│ │ waiter │ │ │ │ out_buf │ │
│ │ окно lwIP=0│ │ │ │ pending_buf │ │
│ └────────────┘ │ │ └─────────────┘ │
│ │ │ │ send() │
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
│ │handle_data │ │◄────DATA──────── │ │ read_cb │ │
│ │handle_close│ │ │ │(sock→ETCP) │ │
│ └─────┬──────┘ │ │ │ │ │
│ │ │ │ │ send fail: │ │
│ ┌─────▼──────┐ │ │ │ pause_buf+ │ │
│ │ to_lwip │ │ │ │ waiter+ │ │
│ │ (очередь) │ │ │ │ пауза read_id│ │
│ └─────┬──────┘ │ │ └─────────────┘ │
│ │tcp_write│ │ │
│ ▼ │ └───────────────────┘
│ lwIP→TUN→APP │
└─────────────────┘
```
---
### 5. BACKPRESSURE (lwIP→ETCP и socket→ETCP)
```
══════════════════════════════════════════════════════════════════════
Используется встроенный механизм ll_queue threshold waiter:
etcp_router_waiter_register(inst, peer_node_id, &waiter)
→ внутри: queue_waiter_wait(normalizer->input_queue, &waiter)
etcp_router_waiter_cancel(inst, peer_node_id, &waiter)
CLIENT (lwIP → ETCP):
recv_cb(buf, len):
ret = send_data(buf, len)
if ret == 0: tcp_recved(len)
if ret < 0:
tx_buf = copy(buf), tx_len = len
НЕ tcp_recved() → окно lwIP = 0, recv_cb больше не вызывается
etcp_router_waiter_register(&tx_waiter)
tx_waiter callback:
ret = send_data(tx_buf, tx_len)
if ret == 0:
tcp_recved(tx_len) → окно открыто, lwIP возобновляет recv_cb
free(tx_buf)
if tun_closed → send_close()
// если ret < 0 → waiter остаётся, normalizer вызовет снова
SERVER (socket → ETCP):
read_cb(buf, n):
ret = send_msg(DATA, buf, n)
if ret == 0: ok
if ret < 0:
pause_buf = copy(buf), pause_len = n
uasync_remove_socket_t(sock) → пауза чтения с сокета
etcp_router_waiter_register(&pause_waiter)
pause_waiter callback:
ret = send_msg(DATA, pause_buf, pause_len)
if ret == 0:
free(pause_buf)
uasync_add_socket_t(sock) → возобновляем чтение
if sock_closed → send_close()
// если ret < 0 → waiter остаётся
```
---
### 6. Последовательность: HANDSHAKE (упрощённый, без CONNECTED)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
tcp_proxy_client_accept_cb() CONNECT
├─ u_calloc(conn) ────────────────────────────→ tcp_proxy_server_handle_connect()
├─ stream_id++ ├─ socket(AF_INET,SOCK_STREAM)
├─ send_connect() ├─ socket_set_nonblocking()
│ [dest_ip(4)+dest_port(2)] ├─ uasync_add_socket_t(read/write/error)
├─ conn→conns list ├─ connect(неблок.) → EINPROGRESS
│ └─ rc→conns list
▼
ACTIVE — данные идут СРАЗУ (без ожидания CONNECTED)
│
│ DATA ...connect завершён...
├──────────────────────────────────────────────────→ tcp_proxy_server_sock_write_cb()
│ ├─ connected=1
│ DATA └─ flush pending_buf в сокет
├──────────────────────────────────────────────────→
│
│ DATA ←─ tcp_proxy_server_sock_read_cb()
│◄────────────────────────────────────────────── (socket recv → ETCP)
│
└──→ трафик идёт в обе стороны
При ошибке connect:
...connect fail... → tcp_proxy_server_send_error() → client получает ERROR, error=1, cleanup
```
---
### 7. Последовательность: CLOSE (drain очередей → close в конце)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
══ Вариант А: lwIP закрывает первым ══
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close CLOSE
│ если tx_buf pend → waiter досылает ──────────→ tcp_proxy_server_handle_close()
│ потом send_close ├─ cli_closed=1
└─ │ если out_buf пуст → shutdown(SHUT_WR)
│ иначе ждём sock_retry_cb
│
...out_buf дослан...
sock_retry_cb:
shutdown(SHUT_WR)
│
...сокет дочитывает остаток...
│
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если pause_buf пуст → send_close
│ иначе ждём waiter_cb
│
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
poll_cb: tun=1, rem=1, to_lwip пуст, unsent=NULL, unacked=NULL
├─ tcp_close(pcb)
└─ conn_free()
══ Вариант Б: серверный сокет закрывается первым ══
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если out_buf+pause_buf пусты → send_close
│ иначе ждём retry/waiter
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
▼
...приложение закрывает TCP...
│
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close
└─
poll_cb: оба закрыты, буферы пусты → cleanup
══ Вариант В: Ошибка / аномалия ══
ERROR
─────────────────────── (или ←) ──────────
error=1
│
▼
poll_cb: tcp_abort(pcb) → немедленная очистка
или: rc→error=1 → conn_free
══ Вариант Г: conn не найден / closed / error ══
Любой пришедший пакет (DATA/CLOSE/ERROR) для conn в закрытом состоянии:
→ tcp_proxy_*_send_msg(ERROR, stream_id)
→ удалённая сторона получает ERROR и немедленно закрывается
```
---
### 8. Диспетчеризация в `tcp_proxy_client_etcp_recv_cb()`
```
┌────────────────────────────────────────────────────────────────────────┐
│ tcp_proxy_client_etcp_recv_cb() │
│ (единая точка входа для обоих направлений) │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ subcmd == CONNECT? │
│ └── tcp_proxy_server_handle_connect() ←── всегда сервер │
│ │
│ stream_id в server.conns? (если server.enabled) │
│ ├── DATA → tcp_proxy_server_handle_data() │
│ ├── CLOSE → tcp_proxy_server_handle_close() │
│ └── ERROR → rc->error=1; conn_free() │
│ │
│ stream_id в client.conns? (если client proxy != NULL) │
│ ├── DATA → tcp_proxy_client_handle_data() │
│ ├── CLOSE → tcp_proxy_client_handle_close() │
│ └── ERROR → tcp_proxy_client_handle_error() │
│ │
│ Приоритет: │
│ 1. CONNECT всегда интерпретируется сервером │
│ 2. Если server.enabled И stream_id найден в server.conns → server │
│ 3. Иначе если client proxy != NULL → client │
│ 4. Иначе drop │
└────────────────────────────────────────────────────────────────────────┘
```
---
### 9. INIT
```
CLIENT SERVER
─────────────────────────────────────────────────────────
utun_instance.c
│
├─ tcp_proxy_client_create() tcp_proxy_server_init()
│ ├─ tun_init_nat() (TUN iface) ├─ memset(ctx,0)
│ ├─ lwip_tcp_init() (TCP стек) ├─ ctx->enabled = config ...
│ ├─ etcp_router_bind( ├─ etcp_router_bind(
│ │ ETCP_ID_TCP_PROXY, │ ETCP_ID_TCP_PROXY,
│ │ tcp_proxy_client_etcp_recv_cb) │ tcp_proxy_client_etcp_recv_cb)
│ ├─ udp_proxy_init() ├─ udp_proxy_init()
│ └─ icmp_proxy_init() └─ icmp_proxy_init() (если client не активен)
│
└─ ОБА слушают ETCP_ID_TCP_PROXY через один обработчик
```
---
### 10. Поля структур
```
tcp_proxy_client_conn:
stream_id — уникальный ID потока
tun_closed — lwIP/FIN (сторона TUN)
rem_closed — CLOSE от exit
error — немедленная очистка
close_sent — CLOSE/ERROR отправлен
close_pending — отправка не удалась, ретрай
to_lwip — очередь DATA от exit → lwIP (feed_from_transport)
pcb — lwIP tcp_pcb
tx_buf — буфер при backpressure lwIP→ETCP (send_data fail)
tx_len — длина tx_buf
tx_waiter — ll_queue waiter на normalizer.input_queue
tcp_proxy_server_conn:
stream_id — уникальный ID потока
cli_closed — клиент прислал CLOSE
sock_closed — сокет получил EOF
error — ошибка
connected — сокет подключился (connect завершён)
close_pending — CLOSE/ERROR не доставлен, ретрай
pending_buf — буфер данных от клиента до завершения connect
out_buf — буфер отправки в сокет (неблокирующий send, EAGAIN retry)
out_off/out_len — offset/размер out_buf
out_timer — таймер повтора send в сокет
out_backoff — backoff для retry send в сокет
pause_buf — буфер при backpressure socket→ETCP (send_msg fail)
pause_len — длина pause_buf
pause_waiter — ll_queue waiter на normalizer.input_queue
close_timer — таймер повтора CLOSE/ERROR
close_backoff — backoff для retry CLOSE/ERROR
sock — OS сокет к адресату
read_id — handle uasync для событий сокета
```
---
### 11. etcp_router waiter API
```
// etcp_router.h
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
// Внутри:
// conn = route_bgp_find_conn_for_node(peer_node_id)
// queue_waiter_wait(conn->normalizer->input, h)
// queue_waiter_cancel(conn->normalizer->input, h)
//
// normalizer.input_queue уже имеет threshold=0 (ждёт полного освобождения)
// через queue_set_threshold(input_queue, 0, 0) в etcp.c
```
+8 -8
View File
@@ -120,7 +120,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
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);
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e, 0);
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);
@@ -158,7 +158,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
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);
etcp_route_send(inst, client_node_id, e, 0);
} else queue_entry_free(e);
}
} else {
@@ -223,7 +223,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
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);
return etcp_route_send(inst, exit_node_id, e, 0);
}
// ====================================================================
@@ -232,12 +232,12 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
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) {
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
!inst ? "inst" : !inst->tcp_proxy ? "tcp_proxy" : "TUN");
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
return -1;
}
struct tun_if* tun = inst->tcp_proxy->tun;
struct tun_if* tun = inst->tcp_proxy_client->tun;
size_t icmp_len = ICMP_MINLEN + payload_len;
size_t pkt_len = 20 + icmp_len;
@@ -270,8 +270,8 @@ int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
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);
queue_data_put(tun->input_queue, e);
return 0;
}
@@ -306,7 +306,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
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->is_exit = inst->tcp_proxy_server.enabled;
ctx->test_loopback = 0;
ctx->expire_timer = NULL;
g_icmp_ctx = ctx;
File renamed without changes.
+106
View File
@@ -0,0 +1,106 @@
Как узнать, что TCP полностью завершился?
В raw API прямого callback'а вида:
on_connection_closed()
нет.
Обычно используют собственное состояние:
struct conn {
bool closing;
};
и после успешного:
tcp_close(pcb);
conn->closing = true;
считают соединение закрытым для приложения.
Сам TCP завершится позже внутри lwIP.
А вызовется ли err callback?
Обычно нет.
tcp_err()
вызывается при ненормальном завершении:
RST
abort
потеря PCB
сетевые ошибки
но не при штатном FIN/ACK закрытии.
Нужно ли освобождать память после tcp_close()?
Сам pcb освобождать нельзя:
tcp_close(pcb);
/* НЕЛЬЗЯ */
mem_free(pcb);
lwIP сделает это самостоятельно.
Но ваши данные (arg) нужно освободить самостоятельно.
Обычно так:
struct my_conn *c = arg;
tcp_arg(pcb, NULL);
free(c);
tcp_close(pcb);
или
free(c);
tcp_close(pcb);
pcb = NULL;
если после этого код больше не обращается к c.
Безопасная практика
После успешного tcp_close():
if (tcp_close(pcb) == ERR_OK) {
pcb = NULL;
}
и считать объект недействительным.
Потому что lwIP может уничтожить его в любой момент по мере завершения TCP state machine.
Типичный шаблон
static void close_conn(struct my_conn *c)
{
err_t err;
err = tcp_close(c->pcb);
if (err == ERR_OK) {
c->pcb = NULL;
free(c);
} else {
/* ERR_MEM -> попробуем позже */
}
}
Но если free(c) делается сразу, нужно быть уверенным, что никакие callback'и lwIP больше не используют arg. Часто перед закрытием сначала снимают callback'и:
tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_err(pcb, NULL);
а затем вызывают tcp_close().
Это особенно важно в сложных приложениях с собственными структурами соединений.
+644
View File
@@ -0,0 +1,644 @@
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
#include "tcp_proxy_client.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 "tcp_proxy_server.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 tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
static void tcp_proxy_client_err_cb(void *arg, err_t err);
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb);
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc);
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc);
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg);
// ====================================================================
// Помощник ll_entry
// ====================================================================
static struct ll_entry* tcp_proxy_client_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, "tcp_proxy_client_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, "tcp_proxy_client_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 tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[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, force);
}
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_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 client: 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 tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6, 1);
}
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len, 0);
}
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x", pc->stream_id);
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0, 1);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
pc->close_sent = 1;
return 0;
}
static int tcp_proxy_client_send_error(struct tcp_proxy_client_conn* pc) {
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0, 1);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
pc->close_sent = 1;
return 0;
}
static int tcp_proxy_client_send_fin(struct tcp_proxy_client_conn* pc) {
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY FIN RELAY sid=%08x", pc->stream_id);
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_FIN, pc->stream_id, NULL, 0, 1);
}
// ====================================================================
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
// ====================================================================
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)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_client_handle_non_tcp(struct tcp_proxy_client* 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 tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc) {
if (!pc->to_lwip || !pc->pcb) return;
if (pc->rem_closed) 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_TRACE(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 tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
struct tcp_proxy_client *p = (struct tcp_proxy_client *)arg;
if (err != LERR_OK || !newpcb) return LERR_ABRT;
struct tcp_proxy_client_conn *pc = u_calloc(1, sizeof(struct tcp_proxy_client_conn));
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: 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, tcp_proxy_client_recv_cb);
tcp_sent(newpcb, tcp_proxy_client_sent_cb);
tcp_err(newpcb, tcp_proxy_client_err_cb);
tcp_poll(newpcb, tcp_proxy_client_poll_cb, 2);
tcp_nagle_disable(newpcb);
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: 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)); tcp_arg(newpcb, NULL); tcp_abort(newpcb); queue_free(pc->to_lwip); u_free(pc); return LERR_MEM; }
pc->next = p->conns; p->conns = pc; p->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: 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;
}
// ====================================================================
// Backpressure: waiter callback при освобождении normalizer очереди
// ====================================================================
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
if (!pc || !pc->proxy || pc->rem_closed || !pc->tx_buf) return;
int ret = tcp_proxy_client_send_data(pc, pc->tx_buf, pc->tx_len);
if (ret == 0) {
if (pc->pcb) tcp_recved(pc->pcb, pc->tx_len);
u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0;
if (pc->fin_local && !pc->close_sent && !pc->close_pending) {
if (pc->fin_remote || pc->rem_closed)
tcp_proxy_client_send_close(pc);
else
tcp_proxy_client_send_fin(pc);
}
}
}
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) { if (p) pbuf_free(p); return LERR_OK; }
if (p == NULL || err != LERR_OK) {
pc->fin_local = 1;
if (p == NULL && err == ERR_OK) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN sid=%08x pcb_state=%u sndbuf=%u cwnd=%u unsent=%p unacked=%p",
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
(void*)pcb->unsent, (void*)pcb->unacked);
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
if (!pc->tx_buf) {
tcp_proxy_client_send_fin(pc);
if (pc->fin_remote && !pc->close_sent && !pc->close_pending)
tcp_proxy_client_send_close(pc);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR recv sid=%08x pcb_state=%u err=%d",
pc->stream_id, pcb->state, err);
pc->error = 1;
tcp_proxy_client_send_error(pc);
}
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;
}
if (pc->tx_buf) { pbuf_free(p); return LERR_OK; }
uint8_t *data = u_malloc(len);
if (data) {
pbuf_copy_partial(p, data, len, 0);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
int ret = tcp_proxy_client_send_data(pc, data, len);
if (ret == 0) { tcp_recved(pcb, len); u_free(data); }
else {
pc->tx_buf = data; pc->tx_len = len;
etcp_router_waiter_register(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter,
tcp_proxy_client_tx_waiter_cb, pc);
}
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
pbuf_free(p);
return LERR_OK;
}
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
(void)len;
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
tcp_proxy_client_feed_from_transport(pc);
return LERR_OK;
}
static void tcp_proxy_client_err_cb(void *arg, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY tcp_proxy_client_err_cb: pc=%p proxy=%p rem_closed=%d err=%d", (void*)pc, pc ? (void*)pc->proxy : NULL, pc ? pc->rem_closed : 0, err); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: error %d sid=%08x pcb_state=%u fin_local=%d rem_closed=%d",
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->fin_local, pc->rem_closed);
pc->pcb = NULL;// pcb уже уничтожен стеком lwip
pc->error = 1;
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
}
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
if (pc->error) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x", pc->stream_id);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_abort(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc);
return LERR_OK;
}
if (pc->close_pending) {
if (pc->error) tcp_proxy_client_send_error(pc);
else tcp_proxy_client_send_close(pc);
}
return LERR_OK;
}
// ====================================================================
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
// ====================================================================
static void tcp_proxy_client_ensure_outbound_listen(struct tcp_proxy_client* 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 client: 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, tcp_proxy_client_accept_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: dynamic listen on port %u", dport_host);
}
}
// ====================================================================
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
// ====================================================================
static void tcp_proxy_client_tun_input(struct ll_queue* q, void* arg) {
struct tcp_proxy_client* p = (struct tcp_proxy_client*)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_client_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_client_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);
}
// ====================================================================
// Очистка tcp_proxy_client_conn
// ====================================================================
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
if (!pc) return;
struct tcp_proxy_client *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);
if (pc->close_pending && p && p->inst) {
pc->close_pending = 0;
uint8_t subcmd = pc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
tcp_proxy_client_send_msg(p->inst, p->via_node_id, subcmd, pc->stream_id, NULL, 0, 1);
}
struct tcp_proxy_client_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;
}
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
if (pc->proxy && pc->proxy->inst) etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
u_free(pc);
}
// ====================================================================
// Обработчики входящих сообщений (сторона клиента)
// ====================================================================
static struct tcp_proxy_client_conn* tcp_proxy_client_find_conn(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc;
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
return NULL;
}
static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc || pc->rem_closed || pc->error) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — no conn/closed, dropping", stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
if (data_len > 0) {
struct ll_entry* e = tcp_proxy_client_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);
tcp_proxy_client_feed_from_transport(pc);
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb_state=%u", stream_id, pc->fin_local, pc->pcb ? pc->pcb->state : 0);
pc->rem_closed = 1;
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_close(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc);
}
static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_close(pc->pcb);
pc->pcb = NULL;
}
pc->rem_closed = 1;
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
tcp_proxy_client_conn_free(pc);
}
static void tcp_proxy_client_handle_fin(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc || !pc->pcb) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN FROM exit sid=%08x pcb_state=%u — shutdown write (send FIN to local)", stream_id, pc->pcb->state);
pc->fin_remote = 1;
if (pc->pcb->state != TIME_WAIT && pc->pcb->state != CLOSED) {
tcp_shutdown(pc->pcb, 0, 1);
tcp_sent(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
}
if (pc->fin_local && !pc->close_sent && !pc->close_pending)
tcp_proxy_client_send_close(pc);
}
// ====================================================================
// ETCP коллбэк клиентской стороны (принимает DATA/CLOSE/ERROR/FIN от сервера)
// ====================================================================
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY_CLIENT) {
uint64_t peer_id;
memcpy(&peer_id, entry->dgram + 1, 8);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing client conns for peer",
(unsigned long long)peer_id);
if (proxy) {
struct tcp_proxy_client_conn *pc, *next;
for (pc = proxy->conns; pc; pc = next) { next = pc->next; tcp_proxy_client_conn_free(pc); }
proxy->conns = NULL; proxy->conn_count = 0;
}
}
if (entry) { 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);
if (proxy) {
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — client conn not found, silent drop", stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Публичное API
// ====================================================================
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
uint64_t via_node_id)
{
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: ua is NULL"); return NULL; }
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: tun name/ip required"); return NULL; }
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: u_calloc failed"); return NULL; }
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
p->via_node_id = via_node_id;
p->mappings = mappings; p->mapping_count = mapping_count;
p->entry_pool = memory_pool_init(sizeof(struct ll_entry));
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
if (!p->lwip) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed");
tun_close(p->tun);
memory_pool_destroy(p->entry_pool); u_free(p); return NULL;
}
if (mapping_count > 0) {
int j; for (j = 0; j < mapping_count; j++) {
uint16_t port_net = htons(mappings[j].local_port);
struct tcp_pcb *lpcb = tcp_new(p->lwip);
if (!lpcb) continue;
tcp_bind(lpcb, INADDR_ANY, port_net);
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
if (listen_pcb) {
tcp_arg(listen_pcb, p);
tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
}
}
}
if (inst) {
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY_CLIENT, tcp_proxy_client_router_recv_cb) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY_CLIENT);
udp_proxy_init(inst, ua);
icmp_proxy_init(inst, ua);
}
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
return p;
}
void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
if (!p) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: conns=%d", p->conn_count);
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY_CLIENT);
udp_proxy_destroy(p->inst);
icmp_proxy_destroy(p->inst);
struct tcp_proxy_client_conn* pc = p->conns;
while (pc) {
struct tcp_proxy_client_conn* next = pc->next;
if (pc->pcb && pc->pcb->state != CLOSED) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
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;
}
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
if (p->inst) etcp_router_waiter_cancel(p->inst, p->via_node_id, &pc->tx_waiter);
u_free(pc); pc = next;
}
p->conns = NULL;
if (p->lwip) { lwip_tcp_destroy(p->lwip); p->lwip = NULL; }
if (p->tun) tun_close(p->tun);
if (p->entry_pool) memory_pool_destroy(p->entry_pool);
u_free(p);
}
+69
View File
@@ -0,0 +1,69 @@
// tcp_proxy_client.h — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
#ifndef TCP_PROXY_CLIENT_H
#define TCP_PROXY_CLIENT_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
struct UASYNC;
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct tun_if;
struct ll_queue;
struct ll_entry;
struct memory_pool;
struct tcp_proxy_client_mapping_config;
struct tcp_proxy_server;
struct lwip_tcp_ctx;
struct tcp_pcb;
struct tcp_proxy_client_conn {
struct tcp_proxy_client_conn* next;
struct tcp_proxy_client* proxy;
struct tcp_pcb* pcb;
uint32_t stream_id;
struct ll_queue* to_lwip; // DATA от exit → lwIP (управление потоком)
uint8_t fin_local; // локальная сторона отправила FIN (lwIP)
uint8_t fin_remote; // exit отправил FIN (назначение закрылось)
uint8_t rem_closed; // exit отправил CLOSE
uint8_t error; // ошибка, немедленная очистка
uint8_t close_sent; // отправили CLOSE/ERROR в exit
uint8_t close_pending; // CLOSE/ERROR не доставлен, ждём повтора
uint8_t* tx_buf; // буфер при backpressure (lwIP→ETCP send fail)
uint16_t tx_len;
struct queue_waiter_handle tx_waiter;
uint8_t dest_ip[4];
uint16_t dest_port;
};
struct tcp_proxy_client {
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
struct tun_if* tun;
struct tcp_proxy_client_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_client_mapping_config* mappings; // указатель на конфиг
int mapping_count;
};
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
uint64_t via_node_id);
void tcp_proxy_client_destroy(struct tcp_proxy_client* p);
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
#endif // TCP_PROXY_CLIENT_H
+468
View File
@@ -0,0 +1,468 @@
// tcp_proxy_server.c — TCP прокси-сервер (exit node)
#include "tcp_proxy_server.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 "../lib/tcp_io.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
static struct tcp_proxy_server* g_tcp_proxy_server_ctx = NULL;
static void on_fin_cb(struct tcp_conn* tc, void* arg);
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
static void on_flushed_cb(struct tcp_conn* tc, void* arg);
static void on_closed_cb(struct tcp_conn* tc, void* arg);
static void read_queue_drain_cb(struct ll_queue* q, void* arg);
static void pause_resume_cb(struct ll_queue* q, void* arg);
static void close_retry_cb(void* arg);
static void diag_timer_cb(void* arg);
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force);
static void send_close(struct tcp_proxy_server_conn* rc);
static void send_error(struct tcp_proxy_server_conn* rc);
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
static inline int write_pending(struct tcp_conn* tc) {
return tc && (tc->write_buf || tc->write_queue->head);
}
// ====================================================================
// Отправка сообщений через ETCP
// ====================================================================
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY_CLIENT;
e->dgram[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, force);
}
static void close_retry_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc) return;
rc->close_timer = NULL;
if (!rc->close_pending) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
if (send_msg(inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1) < 0) {
rc->close_backoff = rc->close_backoff < 5000 ? rc->close_backoff * 2 : 5000;
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
return;
}
rc->close_pending = 0;
}
static void send_close(struct tcp_proxy_server_conn* rc) {
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
if (send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0, 1) < 0) {
rc->close_pending = 1;
rc->close_backoff = 50;
if (!rc->close_timer)
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
}
}
static void send_error(struct tcp_proxy_server_conn* rc) {
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
if (send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0, 1) < 0) {
rc->close_pending = 1;
rc->close_backoff = 50;
if (!rc->close_timer)
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
}
}
static void send_fin(struct tcp_proxy_server_conn* rc) {
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_FIN, rc->stream_id, NULL, 0, 1);
}
// ====================================================================
// Коллбэки tcp_io
// ====================================================================
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (rc->cli_closed) return;
int pend = write_pending(tc);
if (tc->fin_local) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc);
} else if (pend) {
tcp_conn_set_flushed(tc, on_flushed_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
(int)tc->sock, rc->stream_id, pend);
} else {
send_fin(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
(int)tc->sock, rc->stream_id);
}
}
static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (rc->cli_closed) return;
if (tc->fin_local) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc); return;
}
send_fin(rc);
}
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
tc ? (int)tc->sock : -1, rc->stream_id);
tcp_proxy_server_conn_free(rc);
}
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
(void)err;
if (tc->closed) return;
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
send_error(rc);
tcp_proxy_server_conn_free(rc);
}
static void diag_timer_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
struct tcp_conn* tc = rc->tc;
if (!tc || tc->sock == SOCKET_INVALID) return;
int ent_f = tc->entry_pool->free_count;
int dat_f = tc->data_pool->free_count;
int rcv_buf = 0, snd_buf = 0;
socklen_t optlen = sizeof(int);
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &optlen);
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &optlen);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
"SOCK:DIAG fd=%d sid=%08x conn=%d fin=%d "
"rq=%d(%zub) wq=%d(%zub) wbuf=%s "
"ent_f=%d dat_f=%d tcp_rb=%zu tcp_sb=%zu",
(int)tc->sock, rc->stream_id, tc->connected, tc->fin_remote,
tc->read_queue->count, queue_total_bytes(tc->read_queue),
tc->write_queue->count, queue_total_bytes(tc->write_queue),
tc->write_buf ? "y" : "n",
ent_f, dat_f,
(size_t)rcv_buf, (size_t)snd_buf);
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
}
// ====================================================================
// Дрейн read_queue → ETCP (автозабор + backpressure)
// ====================================================================
static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (rc->cli_closed) {
do {
memory_pool_free(rc->tc->data_pool, e->dgram);
queue_entry_free(e);
e = queue_data_get(q);
} while (e);
queue_resume_callback(q);
return;
}
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, e->dgram, e->len, 0);
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%u total=%d",
(int)rc->tc->sock, rc->stream_id, e->len, rc->ctx ? rc->ctx->conn_count : 0);
memory_pool_free(rc->tc->data_pool, e->dgram);
queue_entry_free(e);
if (ret == 0) {
queue_resume_callback(q);
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
(int)rc->tc->sock, rc->stream_id);
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
&rc->pause_waiter, pause_resume_cb, rc);
}
}
static void pause_resume_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc->tc || rc->tc->sock == SOCKET_INVALID) return;
queue_resume_callback(rc->tc->read_queue);
}
// ====================================================================
// Управление жизненным циклом коннекта
// ====================================================================
static int conn_total(struct tcp_proxy_server_conn* rc) {
if (!rc || !rc->ctx) return 0;
int n = 0; struct tcp_proxy_server_conn* c;
for (c = rc->ctx->conns; c; c = c->next) n++;
return n;
}
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
if (!rc) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
if (rc->freed) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
return;
}
rc->freed = 1;
int total = conn_total(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
rc->tc ? (int)rc->tc->sock : -1, rc->stream_id, total, rc->cli_closed, rc->tc ? rc->tc->fin_remote : 0, rc->tc ? rc->tc->error : 0);
if (rc->close_pending && rc->ctx && rc->ctx->inst) {
rc->close_pending = 0;
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
send_msg(rc->ctx->inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1);
}
if (rc->ctx) {
struct tcp_proxy_server_conn** prev = &rc->ctx->conns;
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
}
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
u_free(rc);
}
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id) {
struct tcp_proxy_server_conn* c;
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
return NULL;
}
// ====================================================================
// Входные точки из ETCP-диспетчера
// ====================================================================
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry, uint32_t stream_id, uint64_t src_node_id) {
if (!inst || !inst->tcp_proxy_server.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: 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 tcp_proxy_server_conn* rc = u_calloc(1, sizeof(struct tcp_proxy_server_conn));
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: u_calloc failed 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;
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(sock);
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, on_fin_cb, on_error_cb, rc);
if (!rc->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: tcp_conn_create failed"); socket_close_wrapper(sock); ctx->conn_count--; u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->tc->on_closed = on_closed_cb;
queue_set_callback(rc->tc->read_queue, read_queue_drain_cb, rc);
queue_set_waiter_defer(rc->tc->read_queue, 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(sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: 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));
send_msg(inst, src_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0, 1);
ctx->conn_count--; tcp_conn_destroy(rc->tc); u_free(rc);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
rc->next = ctx->conns; ctx->conns = rc;
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
(void)conn;
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc || !rc->tc || rc->tc->sock == SOCKET_INVALID || rc->tc->error || rc->cli_closed) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TPS handle_data: no/closed conn sid=%08x, dropping", stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (data_len > 0) {
struct ll_entry* e = queue_entry_new_from_pool(rc->tc->entry_pool);
uint8_t* buf = memory_pool_alloc(rc->tc->data_pool);
if (e && buf) {
memcpy(buf, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
e->dgram = buf; e->len = (uint16_t)data_len;
queue_data_put(rc->tc->write_queue, e);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: handle_data alloc failed sid=%08x", stream_id);
if (e) queue_entry_free(e);
if (buf) memory_pool_free(rc->tc->data_pool, buf);
}
}
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
rc->cli_closed = 1;
if (!rc->tc) { tcp_proxy_server_conn_free(rc); return; }
tcp_conn_pause_read(rc->tc);
queue_set_callback(rc->tc->read_queue, NULL, NULL);
{
struct ll_entry* e;
while ((e = queue_data_get(rc->tc->read_queue))) {
memory_pool_free(rc->tc->data_pool, e->dgram);
queue_entry_free(e);
}
}
if (rc->tc->connected) tcp_conn_push_close(rc->tc); else tcp_proxy_server_conn_free(rc);
}
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
tcp_proxy_server_handle_close(inst, stream_id);
}
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc || !rc->tc || !rc->tc->connected) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
(int)rc->tc->sock, stream_id);
tcp_conn_push_fin(rc->tc);
}
// ====================================================================
// ETCP коллбэк серверной стороны (принимает CONNECT/DATA/CLOSE/ERROR/FIN от клиента)
// ====================================================================
void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY) {
uint64_t peer_id;
memcpy(&peer_id, entry->dgram + 1, 8);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing server conns for peer",
(unsigned long long)peer_id);
if (inst && inst->tcp_proxy_server.enabled) {
struct tcp_proxy_server_conn *rc, *next;
for (rc = inst->tcp_proxy_server.conns; rc; rc = next) {
next = rc->next;
if (rc->peer_node_id == peer_id) tcp_proxy_server_conn_free(rc);
}
}
}
if (entry) { 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);
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
tcp_proxy_server_handle_connect(inst, entry, stream_id, src_node_id);
return;
}
if (inst && inst->tcp_proxy_server.enabled) {
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
if (rc) {
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_server_handle_data(inst, conn, entry, stream_id); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_server_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_server_handle_error(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
}
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — server conn not found, silent drop", stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Инициализация / деинициализация
// ====================================================================
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
memset(ctx, 0, sizeof(*ctx));
if (!inst->config) return 0;
ctx->enabled = inst->config->global.tcp_proxy_server_enabled;
ctx->inst = inst;
if (!ctx->enabled) return 0;
g_tcp_proxy_server_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
if (!inst->config->global.tcp_proxy_client_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server initialized node=%016llx", (unsigned long long)inst->node_id);
return 0;
}
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroying: conn_count=%d", ctx->conn_count);
struct tcp_proxy_server_conn* rc = ctx->conns;
while (rc) { struct tcp_proxy_server_conn* next = rc->next; tcp_proxy_server_conn_free(rc); rc = next; }
ctx->conns = NULL; ctx->enabled = 0;
if (g_tcp_proxy_server_ctx == ctx) g_tcp_proxy_server_ctx = NULL;
udp_proxy_destroy(inst);
icmp_proxy_destroy(inst);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroyed");
}
+67
View File
@@ -0,0 +1,67 @@
// tcp_proxy_server.h — TCP прокси-сервер (exit node)
#ifndef TCP_PROXY_SERVER_H
#define TCP_PROXY_SERVER_H
#include <stdint.h>
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include "../lib/tcp_io.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
#define TCP_PROXY_SUBCMD_CONNECT 0x01
#define TCP_PROXY_SUBCMD_DATA 0x03
#define TCP_PROXY_SUBCMD_CLOSE 0x04
#define TCP_PROXY_SUBCMD_ERROR 0x05
#define TCP_PROXY_SUBCMD_FIN 0x06
#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)
struct tcp_proxy_server_conn {
struct tcp_proxy_server_conn* next;
struct tcp_proxy_server* ctx;
struct tcp_conn* tc;
uint32_t stream_id;
uint64_t peer_node_id;
uint8_t dest_ip[4];
uint16_t dest_port;
uint8_t cli_closed; // client sent CLOSE
uint8_t close_pending; // CLOSE/ERROR не доставлен, ретрай
void* close_timer; // таймер повтора CLOSE/ERROR
int close_backoff; // backoff: 50..5000 tb (5ms..500ms)
void* diag_timer; // 1-секундный таймер диагностики
uint8_t freed; // 1 = уже освобождён, защита от double-free
struct queue_waiter_handle pause_waiter;
struct UASYNC* ua;
};
struct tcp_proxy_server {
int enabled;
int conn_count;
struct tcp_proxy_server_conn* conns;
struct UTUN_INSTANCE* inst;
};
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst);
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst);
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id);
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id);
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id);
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id);
#endif
+5 -5
View File
@@ -64,7 +64,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n);
e->len = UDP_PROXY_HDR_SIZE + n;
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e);
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e, 0);
}
// ====================================================================
@@ -169,7 +169,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
memcpy(e->dgram + 20, &dst_port, 2);
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len);
e->len = UDP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, exit_node_id, e);
return etcp_route_send(inst, exit_node_id, e, 0);
}
// ====================================================================
@@ -179,7 +179,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t src_ip, uint16_t src_port,
uint32_t dst_ip, uint16_t dst_port,
const uint8_t* payload, size_t payload_len) {
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) return -1;
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) return -1;
// Собрать IP/UDP ответный пакет
size_t ip_len = 20 + 8 + payload_len;
@@ -208,7 +208,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
e->dgram = u_malloc(1 + ip_len);
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, ip_len); e->len = 1 + ip_len;
u_free(pkt);
queue_data_put(inst->tcp_proxy->tun->input_queue, e);
queue_data_put(inst->tcp_proxy_client->tun->input_queue, e);
return 0;
}
@@ -242,7 +242,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
ctx->inst = inst; ctx->ua = ua;
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
ctx->expire_timer = NULL;
ctx->is_exit = inst->remote_proxy.enabled;
ctx->is_exit = inst->tcp_proxy_server.enabled;
g_udp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);
File renamed without changes.
-387
View File
@@ -1,387 +0,0 @@
// 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
@@ -1,76 +0,0 @@
// 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
+2 -2
View File
@@ -510,8 +510,8 @@ int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t*
path->hop_count = hop_count;
uint64_t* stored = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
memcpy(stored, hop_list, hop_size);
queue_data_put(nq->paths, pe);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Added path for node %016llx via conn %s with %d hops", (unsigned long long)nq->node.node_id, conn->log_name, hop_count);
queue_data_put(nq->paths, pe);
return 0;
}
@@ -923,8 +923,8 @@ static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* co
struct ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (item_entry) {
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added to senders_list: %s", conn->log_name);
queue_data_put(bgp->senders_list, item_entry);
}
}
}
+4 -4
View File
@@ -186,6 +186,10 @@ int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
e->dgram = (uint8_t*)req_pkt;
e->len = pkt_size;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
(unsigned)new_req_id,
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
int ret = etcp_send(to_conn, e);
if (ret != 0) {
u_free(req_pkt);
@@ -208,10 +212,6 @@ int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
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;
}
+4 -3
View File
@@ -147,7 +147,7 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t
} else {
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);
int send_err = etcp_route_send(instance, nq->node.node_id, entry, 1);
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);
@@ -165,17 +165,18 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)src_node_id);
}
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to TUN", ip_len);
int put_err = queue_data_put(instance->tun->input_queue, entry);
instance->routed_packets++;
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++;
instance->routed_packets--;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
}
// Callback for packets from ETCP (via etcp_router_bind id=0)
-562
View File
@@ -1,562 +0,0 @@
// 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
@@ -1,63 +0,0 @@
// 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
+9 -8
View File
@@ -60,6 +60,15 @@ static void tun_read_callback(int fd, void* user_arg)
pkt->dgram = packet_data;
pkt->len = nread + 1;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zd bytes from TUN %s", nread + 1, tun->ifname);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN->] %s", dump_ip_packet_to_buffer(packet_data + 1, nread));
}
// Update statistics
tun->bytes_read += nread + 1;
tun->packets_read++;
// Add to output queue (TUN → routing)
if (queue_data_put(tun->output_queue, pkt) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue");
@@ -68,14 +77,6 @@ static void tun_read_callback(int fd, void* user_arg)
tun->read_errors++;
return;
}
// Update statistics
tun->bytes_read += nread + 1;
tun->packets_read++;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zd bytes from TUN %s", nread + 1, tun->ifname);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN->] %s", dump_ip_packet_to_buffer(packet_data + 1, nread));
}
}
#endif
+28 -25
View File
@@ -145,29 +145,29 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1;
}
// Remote proxy (exit node, optional) — must be before tcp_proxy so
// tcp_proxy_create can overwrite the handler if it has remote mappings
if (remote_proxy_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize remote_proxy (non-fatal)");
// TCP proxy server (exit node, optional) — must be before tcp_proxy_client so
// tcp_proxy_client_create can overwrite the handler if it has remote mappings
if (tcp_proxy_server_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize tcp_proxy_server (non-fatal)");
}
// TCP proxy (from [tcp_proxy] config section)
if (config->global.tcp_proxy_enabled) {
const char* tun_name = config->global.tcp_proxy_tun_name[0] ? config->global.tcp_proxy_tun_name : "tun_tcp";
const char* tun_ip = config->global.tcp_proxy_tun_ip[0] ? config->global.tcp_proxy_tun_ip : "10.99.0.1";
int mtu = config->global.tcp_proxy_mtu > 0 ? config->global.tcp_proxy_mtu : 1500;
instance->tcp_proxy = tcp_proxy_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
config->global.tcp_proxy_mappings, config->global.tcp_proxy_mapping_count,
config->global.tcp_proxy_via_node_id);
if (instance->tcp_proxy) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy enabled: TUN=%s IP=%s MTU=%d mappings=%d",
tun_name, tun_ip, mtu, config->global.tcp_proxy_mapping_count);
// TCP proxy client (from [tcp_proxy] config section)
if (config->global.tcp_proxy_client_enabled) {
const char* tun_name = config->global.tcp_proxy_client_tun_name[0] ? config->global.tcp_proxy_client_tun_name : "tun_tcp";
const char* tun_ip = config->global.tcp_proxy_client_tun_ip[0] ? config->global.tcp_proxy_client_tun_ip : "10.99.0.1";
int mtu = config->global.tcp_proxy_client_mtu > 0 ? config->global.tcp_proxy_client_mtu : 1500;
instance->tcp_proxy_client = tcp_proxy_client_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
config->global.tcp_proxy_client_mappings, config->global.tcp_proxy_client_mapping_count,
config->global.tcp_proxy_client_via_node_id);
if (instance->tcp_proxy_client) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d mappings=%d",
tun_name, tun_ip, mtu, config->global.tcp_proxy_client_mapping_count);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy");
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy client");
return -1;
}
} else {
instance->tcp_proxy = NULL;
instance->tcp_proxy_client = NULL;
}
return 0;
@@ -279,6 +279,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Диагностика ресурсов ДО cleanup
utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP");
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_BEFORE");
// Stop running if not already
instance->running = 0;
@@ -339,15 +340,15 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
instance->tun = NULL;
}
// Cleanup TCP proxy module
if (instance->tcp_proxy) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Destroying TCP proxy module");
tcp_proxy_destroy(instance->tcp_proxy);
instance->tcp_proxy = NULL;
// Cleanup TCP proxy client module
if (instance->tcp_proxy_client) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Destroying TCP proxy client module");
tcp_proxy_client_destroy(instance->tcp_proxy_client);
instance->tcp_proxy_client = NULL;
}
// Cleanup remote proxy
remote_proxy_destroy(instance);
// Cleanup TCP proxy server
tcp_proxy_server_destroy(instance);
// Cleanup routing module (unbinds from etcp_router before etcp_router_destroy)
routing_destroy(instance);
@@ -397,7 +398,9 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
memory_pool_destroy(instance->data_pool);
instance->data_pool = NULL;
}
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_AFTER");
// Note: uasync is NOT destroyed here - caller must destroy it separately
// This allows sharing uasync between multiple instances
instance->ua = NULL;
+6 -6
View File
@@ -11,9 +11,9 @@
#include "firewall.h"
#include "eim_nat.h"
#include "nat_transport.h"
#include "tcp_proxy.h"
#include "proxy/tcp_proxy_client.h"
#include "etcp_router.h"
#include "remote_proxy.h"
#include "proxy/tcp_proxy_server.h"
// Forward declarations
struct utun_config;
@@ -97,15 +97,15 @@ struct UTUN_INSTANCE {
// Socket initialization status: 0=OK, 1=partial (some sockets failed), -1=error (none created)
int socket_init_status;
// TCP proxy (optional, NULL if not enabled)
struct tcp_proxy* tcp_proxy;
// TCP proxy client (optional, NULL if not enabled)
struct tcp_proxy_client* tcp_proxy_client;
// etcp_router bindings и seq-connections (per-instance service routing)
struct ETCP_ROUTER_BINDINGS router_bindings;
struct ll_queue* router_conns;
// Remote proxy (exit node)
struct remote_proxy_ctx remote_proxy;
// TCP proxy server (exit node)
struct tcp_proxy_server tcp_proxy_server;
};
// Functions
+59 -41
View File
@@ -2,51 +2,53 @@
# All available tests (check_PROGRAMS runs via automake check-TESTS)
check_PROGRAMS = \
test_ll_queue \
test_serialize \
test_packet_dump \
test_debug_categories \
test_config_debug \
test_memory_pool_and_config \
test_etcp_api \
test_etcp_crypto \
test_pkt_normalizer_standalone \
test_route_lib \
test_radix \
test_route6_lib \
test_etcp_router_unit \
test_etcp_minimal \
test_ipv6_sockets \
test_u_async_timeouts \
test_u_async_comprehensive \
test_etcp_two_instances \
test_etcp_simple_traffic \
test_ipv6_sockets \
test_etcp_minimal \
test_etcp_100_packets \
test_etcp_reconnect \
test_pkt_normalizer_etcp \
test_pkt_normalizer_standalone \
test_etcp_api \
test_ll_queue \
test_serialize \
test_intensive_memory_pool \
test_memory_pool_and_config \
test_packet_dump \
test_u_async_comprehensive \
test_lwip_tcp \
test_u_async_performance \
test_u_async_timeouts \
test_debug_categories \
test_config_debug \
test_route_lib \
test_bgp_route_exchange \
test_etcp_router \
test_etcp_bbr \
test_etcp_ping \
test_route_ping \
test_nat_detection \
test_nat_engine \
test_nat_transport \
test_nat_stress \
test_etcp_reinit_inflight \
test_tcp_proxy \
test_lwip_tcp \
test_etcp_router \
test_etcp_router_unit \
test_remote_proxy \
test_tcp_proxy_server \
test_udp_proxy \
test_icmp_proxy \
test_radix \
test_route6_lib \
test_tcp_proxy_client \
test_bgp_route_exchange \
test_bbr_integration \
test_intensive_memory_pool \
test_tcp_io \
bench_timeout_heap \
bench_uasync_timeouts
# Долгие тесты: запускаются только вручную, не включаются в make check
# test_etcp_congestion — 25+ секунд, congestion control simulation
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix g_tcp_proxy singleton
noinst_PROGRAMS = test_etcp_congestion
# test_etcp_congestion — DISABLED: старый congestion control удалён, ждёт новых BBR тестов
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix tcp_proxy_client singleton
noinst_PROGRAMS =
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
if USE_OPENSSL
@@ -81,6 +83,7 @@ SECURE_CHANNEL_OBJS = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/
ETCP_CORE_OBJS = \
$(top_builddir)/src/utun-etcp.o \
$(top_builddir)/src/utun-etcp_connections.o \
$(top_builddir)/src/utun-etcp_bbr.o \
$(top_builddir)/src/utun-etcp_loadbalancer.o \
$(top_builddir)/src/utun-pkt_normalizer.o \
$(top_builddir)/src/utun-etcp_api.o \
@@ -117,11 +120,11 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-control_server.o \
$(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \
$(top_builddir)/src/utun-tcp_proxy.o \
$(top_builddir)/src/proxy/utun-tcp_proxy_client.o \
$(top_builddir)/src/utun-etcp_router.o \
$(top_builddir)/src/utun-remote_proxy.o \
$(top_builddir)/src/utun-udp_proxy.o \
$(top_builddir)/src/utun-icmp_proxy.o \
$(top_builddir)/src/proxy/utun-tcp_proxy_server.o \
$(top_builddir)/src/proxy/utun-udp_proxy.o \
$(top_builddir)/src/proxy/utun-icmp_proxy.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_pbuf.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_in.o \
@@ -165,6 +168,10 @@ $(TINYCRYPT_OBJS): $(top_builddir)/src/utun
endif
# Test definitions
test_etcp_bbr_SOURCES = test_etcp_bbr.c
test_etcp_bbr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_bbr_LDADD = $(top_builddir)/src/utun-etcp_bbr.o $(COMMON_LIBS)
test_etcp_crypto_SOURCES = test_etcp_crypto.c
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
@@ -188,16 +195,18 @@ test_ipv6_sockets_SOURCES = test_ipv6_sockets.c
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_congestion_SOURCES = test_etcp_congestion.c
test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
# test_etcp_congestion — отключён (старый congestion control удалён)
#test_etcp_congestion_SOURCES = test_etcp_congestion.c
#test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
# test_etcp_reinit_inflight — отключён (старый congestion control удалён)
#test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
#test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_SOURCES = test_tcp_proxy.c
test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_client_SOURCES = test_tcp_proxy_client.c
test_tcp_proxy_client_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_lwip_tcp_SOURCES = test_lwip_tcp.c
test_lwip_tcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
@@ -214,8 +223,9 @@ test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS)
test_etcp_router_unit_SOURCES = test_etcp_router_unit.c
test_etcp_router_unit_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_remote_proxy_SOURCES = test_remote_proxy.c
test_remote_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_server_SOURCES = test_tcp_proxy_server.c
test_tcp_proxy_server_CFLAGS = -I$(top_srcdir)/lib
test_tcp_proxy_server_LDADD = $(COMMON_LIBS)
test_udp_proxy_SOURCES = test_udp_proxy.c
test_udp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
@@ -299,6 +309,10 @@ test_intensive_memory_pool_SOURCES = test_intensive_memory_pool.c
test_intensive_memory_pool_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_intensive_memory_pool_LDADD = $(COMMON_LIBS)
test_tcp_io_SOURCES = test_tcp_io.c
test_tcp_io_CFLAGS = -I$(top_srcdir)/lib
test_tcp_io_LDADD = $(COMMON_LIBS)
test_memory_pool_and_config_SOURCES = test_memory_pool_and_config.c
test_memory_pool_and_config_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_memory_pool_and_config_LDADD = $(COMMON_LIBS)
@@ -341,6 +355,10 @@ test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_
# test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
# test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS)
test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
bench_timeout_heap_SOURCES = bench_timeout_heap.c
bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib
bench_timeout_heap_LDADD = $(COMMON_LIBS)
@@ -373,7 +391,7 @@ TEST_LOG_DIR = $(top_builddir)/tests/logs
# Custom check target that runs tests and saves logs to files
check-local: $(check_PROGRAMS)
@$(MKDIR_P) $(TEST_LOG_DIR); \
@rm -f $(TEST_LOG_DIR)/*.log 2>/dev/null || true; $(MKDIR_P) $(TEST_LOG_DIR); \
G='\033[0;32m'; R='\033[0;31m'; Y='\033[0;33m'; N='\033[0m'; \
passed=0; \
failed=0; \
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../../"
echo "=== Building BBR integration test ==="
./build.sh -j4
echo ""
echo "=== Running BBR integration test ==="
cd build/tests
./test_bbr_integration
@@ -0,0 +1,517 @@
/**
* @file test_bbr_integration.c
* @brief BBR integration test — max-speed traffic under emulated network conditions
*
* Network: 50ms delay, 5% loss, 1 Mbit shaper, ~100KB queue
* Duration: 10 seconds, metrics every 100ms
*
* Architecture (single uasync, single thread):
* Sender(client:20000) → Dummynet(:20001) → Receiver(server:20002)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include "../lib/mem.h"
#include "../src/dummynet.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_bbr.h"
#include "../src/secure_channel.h"
#include "../src/config_updater.h"
#include "../src/routing.h"
#include "../src/crc32.h"
/* ===== Test constants ===== */
#define DN_PORT 21001
#define SRV_PORT 21002
#define CLI_PORT 21000
#define PAYLOAD_SIZE 1200
#define TEST_DURATION_MS 5000
#define METRICS_TB 1000 /* 100ms in 0.1ms units */
#define SEND_TIMER_TB 1 /* 0.1ms re-schedule */
#define BURST_MAX 64
#define SEND_QUEUE_THRESHOLD 1 /* pause when normalizer->input >= 1 */
#define EMU_DELAY_MS 50
#define EMU_JITTER_MS 12
#define EMU_BW_KBPS 1000
#define EMU_LOSS_PERM 50 /* 5% */
#define EMU_QUEUE_PKTS 65 /* ~100KB at MTU 1400-1600 */
/* Convert raw BBR bandwidth (BW_UNIT * bytes / usec) to kbps */
#define BW_RAW_TO_KBPS(bw) ((double)(bw) / 2097.152)
/* ===== BBR name helpers ===== */
static const char* bbr_mode_name(uint8_t m) {
switch (m) {
case BBR_STARTUP: return "STRTUP";
case BBR_DRAIN: return "DRAIN ";
case BBR_PROBE_BW: return "PROBW ";
case BBR_PROBE_RTT: return "PRRTT ";
default: return "??????";
}
}
static const char* bbr_cycle_name(uint8_t m, uint8_t c) {
if (m != BBR_PROBE_BW) return " --";
switch (c) {
case BBR_BW_PROBE_UP: return " UP";
case BBR_BW_PROBE_DOWN: return " DN";
case BBR_BW_PROBE_CRUISE: return " CR";
case BBR_BW_PROBE_REFILL: return " RF";
default: return " ??";
}
}
/* ===== Test context ===== */
struct test_ctx {
struct UASYNC* ua;
struct UTUN_INSTANCE* sender;
struct UTUN_INSTANCE* receiver;
struct dummynet* dn;
int test_done;
uint64_t bytes_sent;
uint64_t bytes_received;
uint64_t last_bytes_received;
uint64_t start_time_us;
FILE* log_file;
void* metrics_timer;
struct queue_waiter_handle waiter;
};
/* ===== Time helper ===== */
static uint64_t now_us(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
}
/* ===== Instance creation ===== */
static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
const char* priv_hex, const char* pub_hex) {
struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst));
if (!inst) return NULL;
inst->ua = u;
inst->node_id = node_id;
if (sc_init_local_keys(&inst->my_keys, pub_hex, priv_hex) != SC_OK) { u_free(inst); return NULL; }
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET));
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE);
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE);
if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) { u_free(inst); return NULL; }
struct utun_config* cfg = u_calloc(1, sizeof(*cfg));
if (!cfg) { u_free(inst); return NULL; }
strncpy(cfg->global.my_public_key_hex, pub_hex, MAX_KEY_LEN - 1);
strncpy(cfg->global.my_private_key_hex, priv_hex, MAX_KEY_LEN - 1);
cfg->global.my_node_id = node_id;
cfg->global.mtu = 1400;
cfg->global.keepalive_timeout = 5000;
cfg->global.keepalive_interval = 500;
cfg->global.allowed_keys_allow_all = 1;
inst->config = cfg;
return inst;
}
static int add_server(struct UTUN_INSTANCE* inst, const char* name, int port) {
struct CFG_SERVER* srv = u_calloc(1, sizeof(*srv));
if (!srv) return -1;
strncpy(srv->name, name, MAX_CONN_NAME_LEN - 1);
srv->ip.ss_family = AF_INET;
((struct sockaddr_in*)&srv->ip)->sin_addr.s_addr = inet_addr("127.0.0.1");
((struct sockaddr_in*)&srv->ip)->sin_port = htons(port);
srv->type = CFG_SERVER_TYPE_PUBLIC;
srv->next = inst->config->servers;
inst->config->servers = srv;
return 0;
}
static int add_client(struct UTUN_INSTANCE* inst, const char* peer_pubkey) {
struct CFG_CLIENT* cli = u_calloc(1, sizeof(*cli));
if (!cli) return -1;
strncpy(cli->name, "peer", MAX_CONN_NAME_LEN - 1);
strncpy(cli->peer_public_key_hex, peer_pubkey, MAX_KEY_LEN - 1);
cli->keepalive = 1;
cli->next = inst->config->clients;
inst->config->clients = cli;
return 0;
}
static struct CFG_CLIENT_LINK* add_link(struct CFG_CLIENT* cli, struct CFG_SERVER* local_srv, int remote_port) {
struct CFG_CLIENT_LINK* link = u_calloc(1, sizeof(*link));
if (!link) return NULL;
link->remote_addr.ss_family = AF_INET;
((struct sockaddr_in*)&link->remote_addr)->sin_addr.s_addr = inet_addr("127.0.0.1");
((struct sockaddr_in*)&link->remote_addr)->sin_port = htons(remote_port);
link->local_srv = local_srv;
struct CFG_CLIENT_LINK** tail = &cli->links;
while (*tail) tail = &(*tail)->next;
*tail = link;
return link;
}
/* ===== Callbacks ===== */
static void on_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct test_ctx* ctx = (struct test_ctx*)conn->instance->etcp_new_conn_arg;
if (entry) { ctx->bytes_received += entry->len; queue_entry_free(entry); }
}
static void send_burst(struct test_ctx* ctx);
static void send_timer_cb(void* arg) {
send_burst((struct test_ctx*)arg);
}
static void send_wtr_cb(struct ll_queue* q, void* arg) {
(void)q;
send_burst((struct test_ctx*)arg);
}
static void send_burst(struct test_ctx* ctx) {
if (ctx->test_done) return;
struct ETCP_CONN* conn = ctx->sender->connections;
if (!conn || !conn->initialized) return;
if (conn->normalizer && conn->normalizer->input) {
if (queue_entry_count(conn->normalizer->input) >= SEND_QUEUE_THRESHOLD) {
queue_waiter_wait(conn->normalizer->input, &ctx->waiter,
send_wtr_cb, ctx);
return;
}
}
int sent = 0;
while (sent < BURST_MAX) {
struct ll_entry* e = ll_alloc_lldgram(1 + PAYLOAD_SIZE);
if (!e) break;
e->dgram[0] = ETCP_ID_DATA;
e->len = 1 + PAYLOAD_SIZE;
if (etcp_send(conn, e) == 0) { ctx->bytes_sent += PAYLOAD_SIZE; sent++; }
else { queue_entry_free(e); break; }
}
if (!ctx->test_done)
uasync_set_timeout(ctx->ua, SEND_TIMER_TB, ctx, send_timer_cb, "bbr_send");
}
static void print_metrics(struct test_ctx* ctx) {
double time_s = (double)(now_us() - ctx->start_time_us) / 1000000.0;
uint64_t dr = ctx->bytes_received - ctx->last_bytes_received;
ctx->last_bytes_received = ctx->bytes_received;
double tput_kbps = (double)dr * 8.0 / 100.0; /* dr * 8 / (0.1s * 1000) */
const struct dummynet_stats* ds = dummynet_get_stats(ctx->dn, DUMMYNET_FORWARD);
int qpkt = dummynet_get_queue_size(ctx->dn, DUMMYNET_FORWARD);
uint64_t dropq = ds ? ds->dropped : 0;
uint64_t dropl = ds ? ds->lost : 0;
const char* lk_status = " ??";
uint32_t lk_reinit = 0;
uint32_t lk_rtrns = 0;
double bw_latest_k = 0;
double bw_lo_k = 0;
int round_start = 0;
double deliv_k = 0;
int full_bw = 0;
int loss_in_rnd = 0;
const char* mode_str = " ----";
const char* cyc_str = " --";
double pgain = 0;
double infl_kb = 0;
double lim_kb = 0;
double inhi_kb = 0;
double inlo_kb = 0;
double pace_kbps = 0;
double rtt_ms = 0;
if (ctx->sender->connections && ctx->sender->connections->links) {
struct ETCP_LINK* l = ctx->sender->connections->links;
struct ETCP_CONN* c = ctx->sender->connections;
lk_status = l->link_status ? " UP" : " DN";
lk_reinit = c->reinit_count;
lk_rtrns = l->total_retransmissions;
if (l->initialized && l->bbr) {
struct bbr* b = l->bbr;
mode_str = bbr_mode_name(b->mode);
cyc_str = bbr_cycle_name(b->mode, b->cycle_idx);
pgain = (double)b->pacing_gain / BBR_UNIT;
infl_kb = (double)l->inflight_bytes / 1024.0;
lim_kb = (double)l->inflight_lim_bytes / 1024.0;
inhi_kb = (double)b->inflight_hi / 1024.0;
inlo_kb = (double)b->inflight_lo / 1024.0;
pace_kbps = (double)l->bbr_pacing_rate / 125.0;
rtt_ms = (double)b->min_rtt_us / 1000.0;
bw_latest_k = BW_RAW_TO_KBPS(b->bw_latest);
bw_lo_k = BW_RAW_TO_KBPS(b->bw_lo);
round_start = b->round_start;
deliv_k = (double)b->delivered / 1024.0;
full_bw = b->full_bw_reached;
loss_in_rnd = b->loss_in_round;
}
}
static int header_printed = 0;
if (!header_printed) {
printf("=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n");
printf("%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n",
"Time", "TputK", "Qpkt", "DnQov", "DnQls",
"LkS", "Rein", "Rtrn",
"BWlstK", "Rnd", "DelvK", "BWloK",
"Mode", "Cyc", "Pgain", "InflKB", "LimKB",
"PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd");
printf("------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n");
if (ctx->log_file) {
fprintf(ctx->log_file, "=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n");
fprintf(ctx->log_file, "%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n",
"Time", "TputK", "Qpkt", "DnQov", "DnQls",
"LkS", "Rein", "Rtrn",
"BWlstK", "Rnd", "DelvK", "BWloK",
"Mode", "Cyc", "Pgain", "InflKB", "LimKB",
"PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd");
fprintf(ctx->log_file, "------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n");
}
header_printed = 1;
}
printf("%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n",
time_s, tput_kbps, qpkt,
(unsigned long long)dropq, (unsigned long long)dropl,
lk_status, lk_reinit, lk_rtrns,
bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k,
mode_str, cyc_str, pgain, infl_kb, lim_kb,
pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd);
fflush(stdout);
if (ctx->log_file) {
fprintf(ctx->log_file, "%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n",
time_s, tput_kbps, qpkt,
(unsigned long long)dropq, (unsigned long long)dropl,
lk_status, lk_reinit, lk_rtrns,
bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k,
mode_str, cyc_str, pgain, infl_kb, lim_kb,
pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd);
fflush(ctx->log_file);
}
}
static void metrics_timer_cb(void* arg) {
struct test_ctx* ctx = (struct test_ctx*)arg;
if (ctx->test_done) return;
print_metrics(ctx);
ctx->metrics_timer = uasync_set_timeout(ctx->ua, METRICS_TB, ctx, metrics_timer_cb, "bbr_metrics");
}
/* ===== Main ===== */
int main(void) {
printf("=== BBR Integration Test ===\n");
printf("Network emulation: %ums delay, %u%% loss, %u kbps shaper, %u pkt queue\n\n",
EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS, EMU_QUEUE_PKTS);
srand((unsigned)time(NULL));
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
socket_platform_init();
crc32_init();
struct test_ctx ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.ua = uasync_create();
if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; }
const char* s_priv =
"67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub =
"1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17"
"c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* c_priv =
"4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub =
"c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3"
"c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
printf("Creating instances...\n");
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
ctx.receiver = create_instance(ctx.ua, 0x2222222222222222ULL, s_priv, s_pub);
if (!ctx.sender || !ctx.receiver) { printf("ERROR: create_instance failed\n"); return 1; }
ctx.sender->etcp_new_conn_arg = &ctx;
ctx.receiver->etcp_new_conn_arg = &ctx;
if (add_server(ctx.receiver, "srv", SRV_PORT) < 0 ||
add_server(ctx.sender, "local", CLI_PORT) < 0 ||
add_client(ctx.sender, s_pub) < 0) {
printf("ERROR: config setup failed\n"); return 1;
}
add_link(ctx.sender->config->clients, ctx.sender->config->servers, DN_PORT);
printf("Init receiver...\n");
if (utun_instance_init(ctx.receiver) < 0) { printf("ERROR: receiver init failed\n"); return 1; }
printf("Init sender...\n");
if (utun_instance_init(ctx.sender) < 0) { printf("ERROR: sender init failed\n"); return 1; }
etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv);
etcp_set_new_conn_cbk(ctx.receiver, NULL, &ctx);
printf("Creating dummynet on port %d ...\n", DN_PORT);
ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", DN_PORT);
if (!ctx.dn) { printf("ERROR: dummynet_create failed\n"); return 1; }
dummynet_set_direction(ctx.dn, DUMMYNET_FORWARD,
EMU_DELAY_MS, EMU_JITTER_MS,
EMU_BW_KBPS, EMU_QUEUE_PKTS, EMU_LOSS_PERM,
"127.0.0.1", SRV_PORT);
dummynet_set_direction(ctx.dn, DUMMYNET_BACKWARD,
5, 1, 0, 200, 0,
"127.0.0.1", CLI_PORT);
/* Wait for connection */
printf("Waiting for connection...\n");
uint64_t t0 = now_us();
while ((now_us() - t0) < 10000000ULL) {
uasync_poll(ctx.ua, 1);
if (ctx.sender->connections && ctx.sender->connections->links) {
struct ETCP_LINK* l = ctx.sender->connections->links;
if (l->initialized && l->link_status == 1) break;
}
}
printf("Connection established, stabilizing...\n");
t0 = now_us();
while ((now_us() - t0) < 500000ULL) uasync_poll(ctx.ua, 1);
if (ctx.sender->connections && ctx.sender->connections->normalizer && ctx.sender->connections->normalizer->input) {
queue_set_threshold(ctx.sender->connections->normalizer->input, 0, 0);
queue_set_waiter_defer(ctx.sender->connections->normalizer->input, 1);
}
/* Start test */
printf("\n=== Starting traffic (%d seconds) ===\n\n", TEST_DURATION_MS / 1000);
ctx.log_file = fopen("test_bbr_integration.log", "w");
if (ctx.log_file)
fprintf(ctx.log_file, "# BBR Integration Test — %ums delay %u%% loss %ukbps\n\n",
EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS);
ctx.start_time_us = now_us();
ctx.last_bytes_received = 0;
ctx.metrics_timer = uasync_set_timeout(ctx.ua, METRICS_TB, &ctx, metrics_timer_cb, "bbr_metrics");
send_burst(&ctx);
/* Main loop */
uint64_t last_report = 0;
while (!ctx.test_done) {
uasync_poll(ctx.ua, 10);
uint64_t elapsed = (now_us() - ctx.start_time_us) / 1000;
if (elapsed >= TEST_DURATION_MS) ctx.test_done = 1;
if (elapsed - last_report >= 2000) {
last_report = elapsed;
const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD);
const char* lks = "?";
uint32_t rein = 0, rtrns = 0;
if (ctx.sender->connections && ctx.sender->connections->links) {
lks = ctx.sender->connections->links->link_status ? "UP" : "DN";
rein = ctx.sender->connections->reinit_count;
rtrns = ctx.sender->connections->links->total_retransmissions;
}
printf(" t=%lus Lk=%s Rein=%u Rtrns=%u recv=%lu dn_tx=%llu dn_lost=%llu\n",
(unsigned long)elapsed, lks, rein, rtrns, (unsigned long)ctx.bytes_received,
ds ? (unsigned long long)ds->sent : 0ULL,
ds ? (unsigned long long)ds->lost : 0ULL);
}
}
/* Drain */
printf("\nDraining...\n");
ctx.test_done = 1;
t0 = now_us();
while ((now_us() - t0) < 2000000ULL) uasync_poll(ctx.ua, 10);
print_metrics(&ctx);
/* Final summary */
uint64_t end_us = now_us();
double dur_s = (double)(end_us - ctx.start_time_us) / 1000000.0;
printf("\n=== BBR Integration Test Results ===\n");
printf("Duration: %.1f s\n", dur_s);
printf("Bytes received: %lu\n", (unsigned long)ctx.bytes_received);
printf("Avg throughput: %.0f kbps\n",
(double)ctx.bytes_received * 8.0 / dur_s / 1000.0);
const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD);
const struct dummynet_stats* ds_bk = dummynet_get_stats(ctx.dn, DUMMYNET_BACKWARD);
if (ds)
printf("Dummynet forward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n",
(unsigned long long)ds->recv, (unsigned long long)ds->sent,
(unsigned long long)ds->lost, (unsigned long long)ds->dropped);
if (ds_bk)
printf("Dummynet backward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n",
(unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent,
(unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped);
if (ctx.sender->connections) {
struct ETCP_CONN* c = ctx.sender->connections;
printf("ETCP: reinit=%u reset=%u links_up=%u\n",
c->reinit_count, c->reset_count, c->links_up);
if (c->links) {
struct ETCP_LINK* l = c->links;
int lnum = 0;
while (l) {
lnum++;
printf(" Link%d: status=%s state=%u rKeep=%u sKeep=%u retrans=%lu"
" inflight=%u/%u lim=%u kB rtt=%u ms\n",
lnum, l->link_status ? "UP" : "DN", l->link_state,
l->recv_keepalive, l->remote_keepalive,
(unsigned long)l->total_retransmissions,
l->inflight_bytes, l->inflight_packets, l->inflight_lim_bytes / 1024,
l->rtt_last);
if (l->bbr)
printf(" BBR: mode=%d cycle=%d full_bw=%d loss_rnd=%d"
" minrtt=%.1fms bw_hi=%.0fK bw_lo=%.0fK pace=%.0fK infl_hi=%.1fK infl_lo=%.1fK\n",
l->bbr->mode, l->bbr->cycle_idx, l->bbr->full_bw_reached, l->bbr->loss_in_round,
(double)l->bbr->min_rtt_us / 1000.0,
(double)l->bbr->bw_hi[0] / 2097.152, (double)l->bbr->bw_lo / 2097.152,
(double)l->bbr_pacing_rate / 125.0,
(double)l->bbr->inflight_hi / 1024.0, (double)l->bbr->inflight_lo / 1024.0);
l = l->next;
}
}
}
int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */
printf("\n[%s]\n", pass ? "PASS" : "FAIL");
if (ctx.log_file) {
fprintf(ctx.log_file, "\n=== Results ===\n");
fprintf(ctx.log_file, "Duration: %.1fs BytesRecv: %lu Throughput: %.0f kbps\n",
dur_s, (unsigned long)ctx.bytes_received,
(double)ctx.bytes_received * 8.0 / dur_s / 1000.0);
if (ds)
fprintf(ctx.log_file, "DN_FWD: rx=%llu tx=%llu lost=%llu ovf=%llu\n",
(unsigned long long)ds->recv, (unsigned long long)ds->sent,
(unsigned long long)ds->lost, (unsigned long long)ds->dropped);
if (ds_bk)
fprintf(ctx.log_file, "DN_BCK: rx=%llu tx=%llu lost=%llu ovf=%llu\n",
(unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent,
(unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped);
fclose(ctx.log_file);
printf("Log written to test_bbr_integration.log\n");
}
/* Cleanup */
if (ctx.sender->connections && ctx.sender->connections->normalizer && ctx.sender->connections->normalizer->input)
queue_waiter_cancel(ctx.sender->connections->normalizer->input, &ctx.waiter);
dummynet_destroy(ctx.dn);
utun_instance_destroy(ctx.sender);
utun_instance_destroy(ctx.receiver);
uasync_destroy(ctx.ua, 1);
return pass ? 0 : 1;
}
+1 -1
View File
@@ -15,7 +15,7 @@ keepalive=1
link=s1:127.0.0.1:15001
peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
[tcp_proxy]
[tcp_proxy_client]
enabled=yes
tun_name=tun_test_proxy
tun_ip=10.200.30.1
+1 -1
View File
@@ -13,7 +13,7 @@ type=public
[allowed_keys]
allow_all=1
[remote_proxy]
[tcp_proxy_server]
enabled=yes
[debug]
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""TCP test client for tcp_proxy recv→send integration test.
Connects to host:port with SO_MARK=1, sends 1MB random data,
receives 4-byte sentinel + 1MB response, verifies.
Supports --count N for multiple sequential requests.
"""
import os
import socket
import struct
import sys
import time
SO_MARK = 36
ONE_MB = 1048576
SENTINEL = 0xBEEF0102
def recv_exact(sock: socket.socket, n: int) -> bytes:
data = bytearray()
while len(data) < n:
chunk = sock.recv(min(65536, n - len(data)))
if not chunk:
break
data += chunk
return bytes(data)
def send_exact(sock: socket.socket, data: bytes) -> None:
sent = 0
while sent < len(data):
n = sock.send(data[sent:])
if n <= 0:
raise OSError("send failed")
sent += n
def run_one(args, payload: bytes, idx: int) -> int:
t0 = time.monotonic()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if args.mark != 0:
s.setsockopt(socket.SOL_SOCKET, SO_MARK, args.mark)
s.settimeout(args.timeout)
try:
s.connect((args.host, args.port))
except OSError as e:
print(f"[FAIL] {args.name}[{idx}]: connect: {e}", flush=True)
s.close()
return 1
try:
send_exact(s, payload)
except OSError as e:
print(f"[FAIL] {args.name}[{idx}]: send: {e}", flush=True)
s.close()
return 1
try:
got = recv_exact(s, 4 + args.size)
except OSError as e:
print(f"[FAIL] {args.name}[{idx}]: recv: {e}", flush=True)
s.close()
return 1
s.close()
elapsed = time.monotonic() - t0
if len(got) != 4 + args.size:
print(f"[FAIL] {args.name}[{idx}]: len={len(got)}/{4 + args.size} in {elapsed:.3f}s", flush=True)
return 1
sentinel, = struct.unpack(">I", got[:4])
if sentinel != SENTINEL:
print(f"[FAIL] {args.name}[{idx}]: sentinel={sentinel:#010x} expected={SENTINEL:#010x}", flush=True)
return 1
received = got[4:]
if not args.verify:
throughput = (args.size * 2) / 1e6 / elapsed
print(f"[PASS] {args.name}[{idx}]: {args.size} bytes in {elapsed:.3f}s ({throughput:.2f} MB/s)", flush=True)
return 0
if payload != received:
for i in range(min(len(payload), len(received))):
if payload[i] != received[i]:
print(f"[FAIL] {args.name}[{idx}]: mismatch at {i}: "
f"sent={payload[i]:02x} recv={received[i]:02x}", flush=True)
return 1
print(f"[FAIL] {args.name}[{idx}]: size mismatch", flush=True)
return 1
throughput = (args.size * 2) / 1e6 / elapsed
print(f"[PASS] {args.name}[{idx}]: {args.size} bytes in {elapsed:.3f}s ({throughput:.2f} MB/s)", flush=True)
return 0
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--host", required=True)
parser.add_argument("--port", type=int, required=True)
parser.add_argument("--size", type=int, default=ONE_MB)
parser.add_argument("--verify", action="store_true")
parser.add_argument("--timeout", type=float, default=60.0)
parser.add_argument("--mark", type=int, default=1, help="SO_MARK (0=off)")
parser.add_argument("--count", type=int, default=1, help="number of sequential requests")
parser.add_argument("--name", default="test")
parser.add_argument("--seed", type=int, default=0, help="random seed")
args = parser.parse_args()
import random
if args.seed:
random.seed(args.seed)
payloads = [os.urandom(args.size) for _ in range(args.count)]
fails = 0
for i in range(args.count):
if run_one(args, payloads[i], i + 1) != 0:
fails += 1
time.sleep(0.1)
if fails > 0:
print(f"[FAIL] {args.name}: {fails}/{args.count} failed", flush=True)
sys.exit(1)
print(f"[PASS] {args.name}: all {args.count} ok", flush=True)
if __name__ == "__main__":
main()
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""TCP server for tcp_proxy integration test: recv 1MB → send 1MB.
Each connection receives exactly 1MB fully, then sends response:
[sentinel(4) || received_data(1MB)] = 1048580 bytes total.
"""
import socket
import struct
import sys
import threading
import signal
running = True
conn_seq = 0
lock = threading.Lock()
ONE_MB = 1048576
SENTINEL = 0xBEEF0102
def recv_exact(conn: socket.socket, n: int) -> bytes:
data = bytearray()
while len(data) < n:
chunk = conn.recv(min(65536, n - len(data)))
if not chunk:
break
data += chunk
return bytes(data)
def send_exact(conn: socket.socket, data: bytes) -> None:
sent = 0
while sent < len(data):
n = conn.send(data[sent:])
if n <= 0:
raise OSError("send failed")
sent += n
def handle(conn: socket.socket):
global conn_seq
with lock:
conn_seq += 1
seq = conn_seq
try:
received = recv_exact(conn, ONE_MB)
print(f"SERVER conn={seq} recv={len(received)}", flush=True)
if len(received) == ONE_MB:
resp = struct.pack(">I", SENTINEL) + received
send_exact(conn, resp)
print(f"SERVER conn={seq} sent={len(resp)}", flush=True)
else:
print(f"SERVER conn={seq} short_recv={len(received)}/{ONE_MB}", flush=True)
except OSError as e:
print(f"SERVER conn={seq} error: {e}", flush=True)
finally:
conn.close()
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <host> <port>", file=sys.stderr)
sys.exit(1)
host = sys.argv[1]
port = int(sys.argv[2])
global running
def _stop(sig, frame):
global running
running = False
signal.signal(signal.SIGTERM, _stop)
signal.signal(signal.SIGINT, _stop)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(128)
print(f"SERVER: listening on {host}:{port}", flush=True)
while running:
try:
s.settimeout(1.0)
conn, addr = s.accept()
except socket.timeout:
continue
except OSError:
break
t = threading.Thread(target=handle, args=(conn,), daemon=True)
t.start()
s.close()
if __name__ == "__main__":
main()
+162
View File
@@ -0,0 +1,162 @@
#!/bin/bash
# run_recv_send_test.sh — интеграционный тест tcp_proxy: recv→send 1MB
#
# 3 клиента параллельно, каждый по 3 запроса (recv 1MB → send 1MB).
# Всего 9 запросов, макс 3 одновременных TCP-соединения через проксю.
#
# Топология:
# recv_send_client.py (SO_MARK=1) → tun_test_proxy
# → tcp_proxy(lwIP) → ETCP → exit(remote_proxy)
# → connect(10.200.100.N) → iptables DNAT → 127.0.0.1:19092 → recv_send_server.py
#
# Запуск: sudo ./run_recv_send_test.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
UTUN_BIN="$SCRIPT_DIR/../../utun"
LOG_DIR="$SCRIPT_DIR/log"
CLIENT_PY="$SCRIPT_DIR/recv_send_client.py"
SERVER_PY="$SCRIPT_DIR/recv_send_server.py"
SERVER_PORT=19092
CLIENT_COUNT=3
REQ_PER_CLIENT=3
REQ_SIZE=1048576
N_IP=20
# -------- networking --------
setup_net() {
echo "=== Setting up networking ==="
sysctl -w net.ipv4.conf.all.rp_filter=0
ip rule add fwmark 1 priority 199 table 100 2>/dev/null || true
local gw; gw=$(ip route show default | awk '/via/ {print $3; exit}')
[ -n "$gw" ] && ip route replace default via "$gw" table 100 2>/dev/null || true
for i in $(seq 1 $N_IP); do
iptables -t nat -C OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$SERVER_PORT" 2>/dev/null \
|| iptables -t nat -A OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$SERVER_PORT"
done
echo " Done"
}
setup_tun_route() {
for i in $(seq 1 60); do
if [ -e "/proc/sys/net/ipv4/conf/tun_test_proxy/rp_filter" ]; then
sysctl -w net.ipv4.conf.tun_test_proxy.rp_filter=0
ip route replace 10.200.100.0/24 dev tun_test_proxy table 100
ip route replace 10.200.100.0/24 dev tun_test_proxy
echo " tun route ready (${i}x0.3s)"
return 0
fi
sleep 0.3
done
echo " WARN: tun_test_proxy not found after 18s"
}
cleanup_net() {
echo "=== Cleaning up networking ==="
for i in $(seq 1 $N_IP); do
iptables -t nat -D OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$SERVER_PORT" 2>/dev/null || true
done
ip rule del fwmark 1 priority 199 table 100 2>/dev/null || true
ip route del 10.200.100.0/24 dev tun_test_proxy table 100 2>/dev/null || true
ip route del 10.200.100.0/24 dev tun_test_proxy 2>/dev/null || true
ip route flush table 100 2>/dev/null || true
sysctl -w net.ipv4.conf.all.rp_filter=1
sysctl -w net.ipv4.conf.tun_test_proxy.rp_filter=1 2>/dev/null || true
echo " Done"
}
cleanup() {
echo ""; echo "=== Cleanup ==="
kill $EXIT_PID 2>/dev/null || true
kill $CLIENT_PID 2>/dev/null || true
kill $SERVER_PID 2>/dev/null || true
wait $EXIT_PID 2>/dev/null || true
wait $CLIENT_PID 2>/dev/null || true
wait $SERVER_PID 2>/dev/null || true
sleep 0.5; cleanup_net
}
wait_for_etcp() {
for i in $(seq 1 30); do
grep -q "Connection established\|initialized and marked as UP (client)" "$LOG_DIR/exit_utun.log" 2>/dev/null && return 0
grep -q "initialized and marked as UP (client)" "$LOG_DIR/client_utun.log" 2>/dev/null && return 0
sleep 0.5
done
return 1
}
# -------- main --------
if [ "$(id -u)" -ne 0 ]; then echo "ERROR: must be root"; exit 1; fi
[ -x "$UTUN_BIN" ] || { echo "ERROR: utun not found. Build first."; exit 1; }
echo "=== tcp_proxy recv→send 1MB integration test ==="
echo "Clients: $CLIENT_COUNT parallel × $REQ_PER_CLIENT requests × $(($REQ_SIZE / 1048576))MB = $(($CLIENT_COUNT * $REQ_PER_CLIENT)) total"
mkdir -p "$LOG_DIR"; rm -f "$LOG_DIR"/*.log
setup_net
echo "Starting recv_send_server on 127.0.0.1:$SERVER_PORT ..."
python3 "$SERVER_PY" 127.0.0.1 "$SERVER_PORT" >"$LOG_DIR/server.log" 2>&1 &
SERVER_PID=$!; sleep 0.3
echo "Starting utun exit ..."
"$UTUN_BIN" -c "$SCRIPT_DIR/exit.conf" -f -l "$LOG_DIR/exit_utun.log" >"$LOG_DIR/exit_stdout.log" 2>&1 &
EXIT_PID=$!
echo "Starting utun client ..."
"$UTUN_BIN" -c "$SCRIPT_DIR/client.conf" -f -l "$LOG_DIR/client_utun.log" >"$LOG_DIR/client_stdout.log" 2>&1 &
CLIENT_PID=$!
trap cleanup EXIT
setup_tun_route
echo "Waiting for ETCP connection ..."
wait_for_etcp || { echo "ERROR: ETCP timeout"; tail -20 "$LOG_DIR/client_utun.log"; exit 1; }
echo "ETCP ready"; sleep 1
echo ""; echo "=== Running test ==="
T0=$(date +%s%3N)
PASS=0; FAIL=0; pids=()
declare -A client_results
for c in $(seq 1 $CLIENT_COUNT); do
ip="10.200.100.${c}"
name="cl${c}"
python3 "$CLIENT_PY" \
--host "$ip" --port "$SERVER_PORT" \
--size "$REQ_SIZE" --verify --timeout 120 \
--count "$REQ_PER_CLIENT" --name "$name" \
>"$LOG_DIR/client_${c}.log" 2>&1 &
pids+=($!)
done
for pid in "${pids[@]}"; do
wait "$pid" && ((++PASS)) || ((++FAIL))
done
ELAPSED=$(($(date +%s%3N) - T0))
echo ""; echo "=========================================="
echo "Results: $PASS passed, $FAIL failed ($((PASS + FAIL)) clients)"
echo "Total time: ${ELAPSED}ms (${ELAPSED}ms for ${REQ_PER_CLIENT}×${CLIENT_COUNT}=$(($CLIENT_COUNT * $REQ_PER_CLIENT)) requests × $(($REQ_SIZE / 1048576))MB)"
echo "Logs: $LOG_DIR/"
echo "=========================================="
for c in $(seq 1 $CLIENT_COUNT); do
log="$LOG_DIR/client_${c}.log"
if [ -f "$log" ]; then
echo " client_${c}: $(tail -1 "$log")"
fi
done
trap - EXIT; cleanup
[ "$FAIL" -gt 0 ] && exit 1
exit 0
+161
View File
@@ -0,0 +1,161 @@
#include "../src/etcp_bbr.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MSEG(s) do { test_total++; printf("TEST %d: %-50s ", test_total, s); fflush(stdout); } while(0)
#define PASS() do { puts("PASS"); test_passed++; } while(0)
#define FAIL(m) do { printf("FAIL: %s\n", m); test_failed++; return -1; } while(0)
static int test_total = 0;
static int test_passed = 0;
static int test_failed = 0;
static uint32_t g_cwnd;
static uint32_t g_pacing;
static uint32_t run_ack_ex(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight, uint32_t prior_del)
{
bbr->now_tb += rtt_us / 100;
struct bbr_rate_sample rs = {
.delivered = acked, .interval_us = rtt_us, .rtt_us = rtt_us,
.acked_sacked = acked, .prior_delivered = prior_del,
.tx_in_flight = inflight > 0 ? inflight : acked,
.lost = lost, .is_app_limited = app,
};
bbr_main(bbr, &rs, &g_cwnd, &g_pacing, 1400, inflight, inflight >= g_cwnd);
return g_cwnd;
}
static uint32_t run_ack(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight)
{ return run_ack_ex(bbr, acked, rtt_us, app, lost, inflight, bbr->delivered); }
/* --- tests --- */
static int test_init(void)
{
MSEG("bbr_init — startup mode");
struct bbr s; memset(&s, 0xFF, sizeof(s)); bbr_init(&s);
if (s.mode != BBR_STARTUP) FAIL("mode");
if (s.cycle_idx != 0) FAIL("cycle_idx");
if (s.min_rtt_us != ~0U) FAIL("min_rtt_us");
if (s.bw_lo != ~0U) FAIL("bw_lo");
if (s.inflight_lo != ~0U) FAIL("inflight_lo");
if (s.inflight_hi != ~0U) FAIL("inflight_hi");
if (s.initialized != 1) FAIL("initialized");
if (s.full_bw_reached) FAIL("full_bw_reached");
PASS(); return 0;
}
static int test_startup_growth(void)
{
MSEG("startup — cwnd grows");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
uint32_t prev = g_cwnd;
for (int r = 0; r < 4; r++) {
s.now_tb += 10000;
for (int a = 0; a < 5; a++) run_ack(&s, 1400, 10000, 0, 0, prev);
if (g_cwnd <= prev) FAIL("cwnd did not grow");
prev = g_cwnd;
}
PASS(); return 0;
}
static int test_full_bw(void)
{
MSEG("full_bw → DRAIN → PROBE_BW");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
// 4 rounds: first resets full_bw (sample_bw >= 0*1.25), next 3 count up
for (int r = 0; r < 4; r++) {
s.now_tb += 10000;
uint32_t rd = s.delivered;
for (int a = 0; a < 4; a++) run_ack_ex(&s, 1400, 10000, 0, 0, g_cwnd, rd);
}
if (!s.full_bw_reached) FAIL("full_bw_reached");
// Mode transitions STARTUP→DRAIN→PROBE_BW might all happen in one ACK
if (s.mode != BBR_DRAIN && s.mode != BBR_PROBE_BW) FAIL("mode not DRAIN/PROBE_BW after full_bw");
PASS(); return 0;
PASS(); return 0;
}
static int test_loss_startup(void)
{
MSEG("loss events in STARTUP → early full_bw");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0; s.loss_round_start = 1;
for (int i = 0; i < 6; i++) { s.now_tb += 1000; run_ack(&s, 1400, 5000, 0, 1, 5600); }
if (!s.full_bw_reached) FAIL("full_bw_reached not set");
PASS(); return 0;
}
static int test_cwnd_bounded(void)
{
MSEG("cwnd capped by inflight_hi");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
s.inflight_hi = 4000; s.inflight_lo = 4000; g_cwnd = 5600; g_pacing = 0;
s.now_tb += 5000; run_ack(&s, 1400, 5000, 0, 0, 5000);
if (g_cwnd > 4000) FAIL("cwnd > inflight_hi");
PASS(); return 0;
}
static int test_loss_cut(void)
{
MSEG("loss cuts inflight_lo");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.inflight_lo = 5000; s.bw_lo = 1000;
s.bw_latest = 500; s.inflight_latest = 3000;
s.delivered = 0; s.loss_round_delivered = 0;
s.cycle_mstamp_tb = s.now_tb; // prevent phase advance
s.probe_wait_us = 2000000;
uint32_t prev = s.inflight_lo;
s.now_tb += 5000; run_ack_ex(&s, 1400, 5000, 0, 1, 5000, 0);
if (s.inflight_lo >= prev) FAIL("inflight_lo not reduced");
PASS(); return 0;
}
static int test_fast_path(void)
{
MSEG("app-limited ACK triggers fast_path");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; s.full_bw_reached = 1;
s.mode = BBR_PROBE_BW; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.try_fast_path = 1; s.bw_hi[0] = 10000; s.bw_lo = 5000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb += 1000; run_ack(&s, 1400, 5000, 1, 0, 2000);
PASS(); return 0;
}
static int test_probe_rtt(void)
{
MSEG("PROBE_RTT entry after 5s");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.probe_rtt_min_us = ~0U; s.probe_rtt_min_stamp_tb = 10000;
s.min_rtt_us = ~0U; s.min_rtt_stamp_tb = 10000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb = 10000 + 5000U * 10; // +5 seconds in 0.1ms
run_ack(&s, 1400, 1000, 0, 0, 5600);
if (s.mode != BBR_PROBE_RTT) FAIL("mode not PROBE_RTT");
if (g_cwnd > BBR_CWND_MIN_TARGET * 1400 + 1400) FAIL("cwnd not capped");
PASS(); return 0;
}
int main(void)
{
test_init();
test_startup_growth();
test_full_bw();
test_loss_startup();
test_cwnd_bounded();
test_loss_cut();
test_fast_path();
test_probe_rtt();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed ? 1 : 0;
}
+8 -9
View File
@@ -117,11 +117,7 @@ static int is_connection_established(struct UTUN_INSTANCE* inst) {
if (!inst) return 0;
struct ETCP_CONN* conn = inst->connections;
while (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
if (link->initialized) return 1;
link = link->next;
}
if (conn->initialized) return 1;
conn = conn->next;
}
return 0;
@@ -211,6 +207,9 @@ static void monitor(void* arg) {
return;
}
printf("Server recreated (node_id=%llx)\n", (unsigned long long)server_instance->node_id);
// Force client to reinit — otherwise conn_ok stays true on old initialized=1
{ struct ETCP_CONN* c = client_instance->connections;
while (c) { etcp_conn_reinit(c); c = c->next; } }
restart_action_done = 1;
}
drain_received(0);
@@ -231,7 +230,8 @@ static void monitor(void* arg) {
packets_received = 0;
drain_received(0);
}
send_packets();
if (is_connection_established(client_instance))
send_packets();
drain_received(1);
if (packets_received >= TOTAL_PACKETS) {
printf("=== Phase 4 done: sent=%d received=%d ===\n", packets_sent, packets_received);
@@ -293,7 +293,8 @@ static void monitor(void* arg) {
packets_received = 0;
drain_received(0);
}
send_packets();
if (is_connection_established(client_instance))
send_packets();
drain_received(1);
if (packets_received >= TOTAL_PACKETS) {
printf("=== Phase 7 done: sent=%d received=%d ===\n", packets_sent, packets_received);
@@ -320,8 +321,6 @@ int main(void) {
if (create_temp_configs() != 0) return 1;
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_categories(DEBUG_CATEGORY_NONE);
printf("=== ETCP Reconnect Test ===\n");
printf("Server port: %d, Client port: %d\n", server_port, client_port);
+3 -3
View File
@@ -136,7 +136,7 @@ static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
memcpy(buf + 10, expected_data, data_len);
struct ll_entry* re = queue_entry_new(0);
if (re) { re->dgram = u_malloc(10 + data_len); memcpy(re->dgram, buf, 10 + data_len); re->len = 10 + data_len;
etcp_route_send(srv, client_node_id, re); reply_sent++; }
etcp_route_send(srv, client_node_id, re, 0); reply_sent++; }
}
} else {
queue_entry_free(entry); queue_dgram_free(entry);
@@ -187,7 +187,7 @@ static int test_loopback(void) {
uint8_t data[6] = { 0xF0, 0xAA, 0xBB, 0xCC, 0x00, 0x00 };
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(6); memcpy(e->dgram, data, 6); e->len = 6;
etcp_route_send(srv, srv->node_id, e); // loopback
etcp_route_send(srv, srv->node_id, e, 0); // loopback
etcp_router_unbind(srv, 0xF0);
if (loop_rcvd != 1 || !loop_ok) {
printf("[FAIL] loopback: rcvd=%d ok=%d\n", loop_rcvd, loop_ok);
@@ -249,7 +249,7 @@ static void monitor(void* arg) {
memcpy(buf + 10, expected_data, data_len);
struct ll_entry* e = queue_entry_new(0);
if (e) { e->dgram = u_malloc(10 + data_len); memcpy(e->dgram, buf, 10 + data_len); e->len = 10 + data_len;
if (etcp_route_send(cli, server_node_id, e) == 0) fwd_sent++;
if (etcp_route_send(cli, server_node_id, e, 0) == 0) fwd_sent++;
else { queue_entry_free(e); queue_dgram_free(e); }
}
}
+185 -33
View File
@@ -31,11 +31,19 @@ static struct {
uint8_t marker;
} rx;
static int g_notify_count = 0;
static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn;
if (!entry || !entry->dgram || entry->len < 2) {
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
rx.errors++;
g_notify_count++;
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
// close/restart: len==9 (svc_id + node_id), conn==NULL
if (entry->len == 9 && !conn) {
g_notify_count++;
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (entry->dgram[1] != rx.marker) { rx.errors++; } else { rx.delivered++; rx.last_seq = rx.expected_seq; rx.expected_seq++; }
@@ -52,7 +60,7 @@ static struct ETCP_CONN fake_conn;
static void inject(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
uint64_t src, uint8_t svc_id, uint32_t seq,
const uint8_t* pl, size_t pl_len, int is_ack) {
const uint8_t* pl, size_t pl_len, int is_ack, uint8_t flags) {
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_ID_SVC_ROUTE;
@@ -60,6 +68,7 @@ static void inject(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
hdr.src_node_id = src;
hdr.seq = seq;
hdr.svc_id = svc_id;
hdr.flags = flags;
size_t total = SVC_ROUTE_HDR_SIZE + (is_ack ? 0 : pl_len);
struct ll_entry* e = queue_entry_new(0);
@@ -185,7 +194,8 @@ static int test_conn_get_create(void) {
if (c->svc_id != TEST_SVC_ID) FAIL("wrong svc_id");
if (c->tx_seq != 0) FAIL("tx_seq != 0");
if (c->rx_seq != 0) FAIL("rx_seq != 0");
if (c->rx_acked != 0) FAIL("rx_acked != 0");
if (c->tx_acked != 0) FAIL("tx_acked != 0");
if (c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq != 0");
if (c->recv_q == NULL) FAIL("recv_q is NULL");
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts not set");
if (c->ack_timer != NULL) FAIL("ack_timer set at creation");
@@ -249,9 +259,9 @@ static int test_in_order(void) {
rx_reset(0xAA);
uint8_t data[] = { TEST_SVC_ID, 0xAA, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0);
if (rx.delivered != 3) FAIL("delivered != 3");
if (rx.errors != 0) FAIL("errors != 0");
@@ -272,14 +282,14 @@ static int test_out_of_order(void) {
rx_reset(0xBB);
uint8_t data[] = { TEST_SVC_ID, 0xBB, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("after seq 0: delivered != 1");
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 3, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 3, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("after seq 2,3: still delivered != 1 (gap at 1)");
// Fill the gap
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 4) FAIL("after seq 1 fill: delivered != 4");
if (rx.errors != 0) FAIL("errors != 0");
if (c->rx_seq != 4) FAIL("rx_seq != 4");
@@ -299,9 +309,9 @@ static int test_duplicate_same(void) {
rx_reset(0xCC);
uint8_t data[] = { TEST_SVC_ID, 0xCC, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0); // dup
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0); // dup
if (rx.delivered != 2) FAIL("delivered != 2 (dup dropped)");
if (c->rx_seq != 2) FAIL("rx_seq != 2");
@@ -321,10 +331,10 @@ static int test_duplicate_past(void) {
rx_reset(0xDD);
uint8_t data[] = { TEST_SVC_ID, 0xDD, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 2) FAIL("delivered != 2");
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0); // past
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0); // past
if (rx.delivered != 2) FAIL("delivered != 2 (past dup dropped)");
if (c->rx_seq != 2) FAIL("rx_seq != 2");
@@ -345,7 +355,7 @@ static int test_out_of_bounds(void) {
rx_reset(0xEE);
uint8_t data[] = { TEST_SVC_ID, 0xEE, 0 };
uint32_t bad_seq = ROUTER_MAX_INFLIGHT + 2;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, bad_seq, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, bad_seq, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("out-of-bounds delivered");
if (c->rx_seq != 0) FAIL("rx_seq changed on bounds drop");
@@ -367,10 +377,10 @@ static int test_circular_wrap(void) {
rx_reset(0x11);
rx.expected_seq = 0xFFFFFFFE;
uint8_t data[] = { TEST_SVC_ID, 0x11, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFE, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFF, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFE, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFF, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 4) FAIL("delivered != 4 (wrap)");
if (c->rx_seq != 2) FAIL("rx_seq != 2 after wrap (wrapped)");
@@ -390,7 +400,7 @@ static int test_data_integrity(void) {
rx_reset(0x77);
uint8_t payload[10] = { 0x77, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, payload, 10, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, payload, 10, 0, 0);
if (rx.delivered != 1) FAIL("delivered != 1");
if (rx.errors != 0) FAIL("data mismatch");
@@ -402,7 +412,7 @@ static int test_data_integrity(void) {
}
static int test_ack_receive(void) {
TEST("ACK receive — rx_acked updated");
TEST("ACK receive — tx_acked updated");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
@@ -410,9 +420,9 @@ static int test_ack_receive(void) {
rx_reset(0x00);
// Send ACK: is_ack=1, seq=7 means remote has rx_seq=7
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 7, NULL, 0, 1);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 7, NULL, 0, 1, 0);
if (c->rx_acked != 7) FAIL("rx_acked not updated from ACK");
if (c->tx_acked != 7) FAIL("tx_acked not updated from ACK");
if (rx.delivered != 0) FAIL("ACK delivered to handler (should not)");
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts not updated on ACK");
@@ -433,10 +443,10 @@ static int test_ack_timer(void) {
rx_reset(0x55);
uint8_t data[] = { TEST_SVC_ID, 0x55, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (c->ack_timer == NULL) FAIL("ack_timer not scheduled after data");
if (c->rx_acked != 0) FAIL("rx_acked changed before timer fired");
if (c->ack_timer == NULL && c->last_sent_ack_seq != 1) FAIL("ack not sent (no timer, no immediate)");
if (c->ack_timer != NULL && c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq changed before timer fired");
// Cancel timer manually (it would fire in real event loop)
if (c->ack_timer) uasync_cancel_timeout(ua, c->ack_timer);
@@ -455,9 +465,9 @@ static int test_send_q(void) {
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
// Simulate full inflight: set rx_acked far behind tx_seq
// Simulate full inflight: set tx_acked far behind tx_seq
c->tx_seq = ROUTER_MAX_INFLIGHT;
c->rx_acked = 0;
c->tx_acked = 0;
if (c->send_q == NULL) FAIL("send_q is NULL");
// Send via conn_send — should queue, not drop
@@ -492,7 +502,7 @@ static int test_loopback(void) {
memcpy(e->dgram, buf, 4);
e->len = 4;
if (etcp_route_send(&inst, inst.node_id, e) != 0) FAIL("loopback send failed");
if (etcp_route_send(&inst, inst.node_id, e, 0) != 0) FAIL("loopback send failed");
if (rx.delivered != 1) FAIL("loopback not delivered");
if (rx.errors != 0) FAIL("loopback data mismatch");
@@ -512,7 +522,7 @@ static int test_timestamp(void) {
rx_reset(0x99);
uint8_t data[] = { TEST_SVC_ID, 0x99, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts zero after data");
// Cancel ack_timer
@@ -541,6 +551,143 @@ static int test_conn_send_no_bgp(void) {
return 0;
}
// ======================== Helper: pump random data ========================
static int pump_data(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
uint8_t marker, int count, uint8_t flags) {
uint8_t data[] = { TEST_SVC_ID, marker, 0 };
for (int i = 0; i < count; i++)
inject(recv_cb, inst, TEST_REMOTE_NODE, TEST_SVC_ID, i, data + 1, 2, 0, flags);
return count;
}
// ======================== Restart/session tests ========================
static int test_rst_flag(void) {
TEST("RST flag — rconn destroyed, service notified");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xA1);
pump_data(recv_cb, &inst, 0xA1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
uint8_t data[] = { TEST_SVC_ID, 0x01, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, n, data + 1, 2, 0, ROUTER_FLAG_RST);
if (g_notify_count != 1) FAIL("RST should notify service");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("new rconn should exist");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_client_restart(void) {
TEST("client restart — START flag detected after random data");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xB1);
pump_data(recv_cb, &inst, 0xB1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xB2);
uint8_t data[] = { TEST_SVC_ID, 0xB2, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START);
if (g_notify_count != 1) FAIL("restart not notified");
if (rx.delivered < 1) FAIL("data after restart not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_sess_id_change(void) {
TEST("sess_id change — 0→1 with START triggers restart");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xC1);
pump_data(recv_cb, &inst, 0xC1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xC2);
uint8_t data[] = { TEST_SVC_ID, 0xC2, 0 };
uint8_t f = (1 << ROUTER_SESS_ID_SHIFT) | ROUTER_FLAG_START;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, f);
if (g_notify_count != 1) FAIL("restart not notified");
if (rx.delivered < 1) FAIL("data after restart not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_sess_id_wrap(void) {
TEST("sess_id wrap — 3→0 with START triggers restart");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
uint8_t f3 = (3 << ROUTER_SESS_ID_SHIFT);
rx_reset(0xD1);
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn not found");
c->peer_sess_id = 3;
pump_data(recv_cb, &inst, 0xD1, n, f3);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xD2);
g_notify_count = 0;
uint8_t data[] = { TEST_SVC_ID, 0xD2, 0 };
uint8_t f0 = (0 << ROUTER_SESS_ID_SHIFT) | ROUTER_FLAG_START;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, f0);
if (g_notify_count != 1) FAIL("restart not notified on wrap");
if (rx.delivered < 1) FAIL("data after wrap not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_server_reinit(void) {
TEST("server reinit — new rconn gets start_sent=0 sess_id=0");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
int n = rand() % 10 + 1;
pump_data(recv_cb, &inst, 0xE1, n, 0);
etcp_router_destroy(&inst);
memset(&inst.api_bindings, 0, sizeof(inst.api_bindings));
memset(&inst.router_bindings, 0, sizeof(inst.router_bindings));
if (etcp_router_init(&inst) != 0) FAIL("reinit failed");
etcp_router_bind(&inst, TEST_SVC_ID, test_handler);
recv_cb = inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE];
if (!recv_cb) FAIL("recv_cb not bound");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn after reinit");
if (c->start_sent != 0) FAIL("start_sent != 0 after reinit");
if (c->sess_id != 0) FAIL("sess_id != 0 after reinit");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
// ======================== Main ========================
int main(void) {
// Run all tests
@@ -562,6 +709,11 @@ int main(void) {
test_loopback();
test_timestamp();
test_conn_send_no_bgp();
test_rst_flag();
test_client_restart();
test_sess_id_change();
test_sess_id_wrap();
test_server_reinit();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed > 0 ? 1 : 0;
+5 -5
View File
@@ -1,5 +1,5 @@
// test_icmp_proxy.c — 2-node ICMP ping test via etcp_router
// Node1 (client): tcp_proxy → sends ICMP_REQUEST to exit
// Node1 (client): tcp_proxy_client → sends ICMP_REQUEST to exit
// Node2 (exit): receives → raw socket → sends echo → receives reply → sends ICMP_REPLY
#include <stdio.h>
#include <stdlib.h>
@@ -21,8 +21,8 @@
#include "../src/etcp_connections.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/icmp_proxy.h"
#include "../src/remote_proxy.h"
#include "../src/proxy/icmp_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@@ -48,7 +48,7 @@ static const char* cfg_node_client(void) {
"[server: s1]\naddr=127.0.0.1:9081\ntype=public\n"
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9082\n"
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"[tcp_proxy]\n"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
@@ -67,7 +67,7 @@ static const char* cfg_node_exit(void) {
"tun_ifname=tun98\n"
"[server: s1]\naddr=127.0.0.1:9082\ntype=public\n"
"[allowed_keys]\nallow_all=1\n"
"[remote_proxy]\nenabled=yes\n");
"[tcp_proxy_server]\nenabled=yes\n");
return buf;
}
+18 -36
View File
@@ -30,32 +30,24 @@ static double test_without_pools(int iterations) {
struct ll_queue* queue = queue_new(ua, 0, 0, 0,"q1"); // Без пулов
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи
// Добавить записи (чтобы waiters становились в очередь)
for (int i = 0; i < 10; i++) {
void* data = queue_entry_new(64);
queue_data_put(queue, data); // Используем ID = i
queue_data_put(queue, data);
}
// Удалить записи (триггер waiters)
// Создать много waiters (общий порог 0, count=10 => все в очередь)
struct queue_waiter_handle handles[32] = {0};
for (int i = 0; i < 32; i++) queue_waiter_wait(queue, &handles[i], intensive_waiter_callback, NULL);
// Удалить записи (триггер waiters — по одному за get)
for (int i = 0; i < 10; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
if (retrieved) queue_entry_free(retrieved);
}
// Отменить waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
}
// Отменить оставшиеся waiters
for (int i = 0; i < 32; i++) queue_waiter_cancel(queue, &handles[i]);
}
queue_free(queue);
@@ -81,34 +73,24 @@ static double test_with_pools(int iterations) {
}
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи из пула
for (int i = 0; i < 10; i++) {
void* data = queue_entry_new_from_pool(pool);
if (data) {
queue_data_put(queue, data); // Используем ID = i
}
if (data) queue_data_put(queue, data);
}
// Создать много waiters
struct queue_waiter_handle handles[32] = {0};
for (int i = 0; i < 32; i++) queue_waiter_wait(queue, &handles[i], intensive_waiter_callback, NULL);
// Удалить записи (триггер waiters)
for (int i = 0; i < 10; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved); // Вернется в пул
}
if (retrieved) queue_entry_free(retrieved); // Вернется в пул
}
// Отменить waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
}
// Отменить оставшиеся waiters
for (int i = 0; i < 32; i++) queue_waiter_cancel(queue, &handles[i]);
}
queue_free(queue);
+148 -5
View File
@@ -73,6 +73,15 @@ static void waiter_cb(struct ll_queue *q, void *arg) {
(*(int*)arg)++; stats.cb_waiter++;
}
static int waiter_order[10];
static int waiter_order_idx;
static void waiter_cb_order(struct ll_queue *q, void *arg) {
(void)q;
waiter_order[waiter_order_idx++] = *(int*)arg;
stats.cb_waiter++;
}
/* ------------------------------------------------------------------ */
static void test_basic(void) {
TEST("basic creation / free");
@@ -165,10 +174,12 @@ static void test_waiter(void) {
TEST("wait_threshold + cancel");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "q6");
queue_set_threshold(q, 2, 0);
int called = 0;
struct queue_waiter *w = queue_wait_threshold(q, 2, 0, waiter_cb, &called);
ASSERT(w == NULL && called == 1, "immediate when condition met"); // empty queue
struct queue_waiter_handle h = {0};
int ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 1 && called == 1, "immediate when condition met"); // empty queue
for (int i = 0; i < 5; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
@@ -176,15 +187,142 @@ static void test_waiter(void) {
}
called = 0;
w = queue_wait_threshold(q, 2, 0, waiter_cb, &called);
ASSERT(w != NULL && called == 0, "");
ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 0 && called == 0, "");
for (int i = 0; i < 3; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
for (int i = 0; i < 15; i++) uasync_poll(ua, 1);
ASSERT_EQ(called, 1, "");
queue_cancel_wait(q, w);
queue_waiter_cancel(q, &h);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_multiple(void) {
TEST("waiter multiple FIFO (round-robin)");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wm");
queue_set_threshold(q, 2, 0);
for (int i = 0; i < 5; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
waiter_order_idx = 0;
int ids[4] = {10, 20, 30, 40};
struct queue_waiter_handle handles[4] = {0};
for (int i = 0; i < 4; i++) ASSERT(queue_waiter_wait(q, &handles[i], waiter_cb_order, &ids[i]) == 0, "should queue");
for (int i = 0; i < 5; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(waiter_order_idx, 3, "3 waiters fired");
ASSERT(waiter_order[0] == 10 && waiter_order[1] == 20 && waiter_order[2] == 30, "FIFO order");
for (int i = 0; i < 4; i++) queue_waiter_cancel(q, &handles[i]);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_cancel(void) {
TEST("waiter cancel middle");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wc");
queue_set_threshold(q, 3, 0);
for (int i = 0; i < 10; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
waiter_order_idx = 0;
int ids[3] = {1, 2, 3};
struct queue_waiter_handle handles[3] = {0};
for (int i = 0; i < 3; i++) queue_waiter_wait(q, &handles[i], waiter_cb_order, &ids[i]); // count=10 > 3, all queued
queue_waiter_cancel(q, &handles[1]); // cancel middle (ID 2)
for (int i = 0; i < 10; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(waiter_order_idx, 2, "2 fired (middle cancelled)");
ASSERT(waiter_order[0] == 1 && waiter_order[1] == 3, "skip cancelled");
for (int i = 0; i < 3; i++) queue_waiter_cancel(q, &handles[i]);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_queue_free(void) {
TEST("queue_free clears waiters");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wf");
for (int i = 0; i < 3; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
int called = 0;
struct queue_waiter_handle h = {0};
queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(h.internal != NULL, "waiter is queued");
queue_free(q);
ASSERT(h.internal == NULL, "internal cleared by queue_free");
uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_threshold(void) {
TEST("waiter dynamic threshold change");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wt");
queue_set_threshold(q, 0, 0); // default, but explicit
for (int i = 0; i < 10; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
int called = 0;
struct queue_waiter_handle h = {0};
int ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 0 && called == 0, "queued (10 > 0)");
queue_set_threshold(q, 10, 0); // raise threshold
queue_entry_free((struct ll_entry*)queue_data_get(q)); // count=9, check: 9 <= 10? YES
ASSERT_EQ(called, 1, "fired after threshold raised");
while (queue_entry_count(q)) queue_entry_free((struct ll_entry*)queue_data_get(q));
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_reuse(void) {
TEST("waiter handle reuse");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wr");
int called = 0;
struct queue_waiter_handle h = {0};
int ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 1 && called == 1, "immediate (empty queue)");
for (int i = 0; i < 3; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
called = 0;
ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 0 && called == 0, "queued");
for (int i = 0; i < 3; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(called, 1, "fired again on same handle");
queue_waiter_cancel(q, &h);
ASSERT(h.internal == NULL, "internal cleared");
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
@@ -284,6 +422,11 @@ int main(void) {
test_lifo_priority();
test_callback();
test_waiter();
test_waiter_multiple();
test_waiter_cancel();
test_waiter_queue_free();
test_waiter_threshold();
test_waiter_reuse();
test_limits_hash();
test_pool();
test_stress();
+11 -26
View File
@@ -16,11 +16,6 @@
static int test_callback_count = 0;
static void test_callback(struct ll_queue* q, void* data, void* arg) {
(void)q; (void)data; (void)arg;
test_callback_count++;
}
static void test_waiter_callback(struct ll_queue* q, void* arg) {
(void)q; (void)arg;
test_callback_count++;
@@ -49,41 +44,31 @@ int main() {
uasync_destroy(ua, 0);
return 1;
}
// Test multiple waiter allocations to trigger pool usage
struct queue_waiter* waiters[10];
queue_set_threshold(queue, 2, 0);
// Test multiple waiters with new linked-list backpressure
struct queue_waiter_handle handles[10] = {0};
for (int i = 0; i < 10; i++) {
waiters[i] = queue_wait_threshold(queue, i * 2, 0, test_waiter_callback, NULL);
queue_waiter_wait(queue, &handles[i], test_waiter_callback, NULL); // queue empty => immediate callback
}
// Add some entries and trigger waiters
// Add some entries — waiters already fired, none queued
for (int i = 0; i < 5; i++) {
void* data = queue_entry_new(10);
queue_data_put(queue, data); // Используем ID = i
queue_data_put(queue, data);
}
// Remove entries to trigger waiter callbacks
// Remove entries
for (int i = 0; i < 5; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
if (retrieved) queue_entry_free(retrieved);
}
// Cancel remaining waiters
// Cancel (safe call even if not waiting)
for (int i = 0; i < 10; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
queue_waiter_cancel(queue, &handles[i]);
}
// Get pool statistics
size_t allocations, reuse_count;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Pool statistics: allocations=%zu, reuse_count=%zu", allocations, reuse_count);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Pool efficiency: %.1f%%",
allocations > 0 ? (100.0 * reuse_count / allocations) : 0.0);
queue_free(queue);
uasync_destroy(ua, 0);
+2 -2
View File
@@ -299,7 +299,7 @@ static int test_provider_egress(void) {
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry);
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed");
queue_dgram_free(entry);
@@ -468,7 +468,7 @@ static int test_full_roundtrip(void) {
struct ll_entry* entry = queue_entry_new(0);
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry);
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry, 0);
if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; }
// Poll for provider to process egress
-176
View File
@@ -1,176 +0,0 @@
// test_remote_proxy.c — Test remote_proxy: CONNECT → socket → echo → verify
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/remote_proxy.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
#include "../src/tun_if.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define ECHO_PORT 19991
#define TEST_TIMEOUT_MS 8000
#define PAYLOAD_SIZE 64
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char cfg_path[256];
static struct UTUN_INSTANCE* inst = NULL;
static struct UASYNC* ua = NULL;
static int g_ok = 0, g_done = 0;
static void* g_mon_id = NULL;
static pid_t echo_pid = 0;
static uint8_t send_buf[PAYLOAD_SIZE], recv_buf[PAYLOAD_SIZE];
static int connected_ok = 0;
static uint32_t stream_id = 1;
static const char* cfg =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[remote_proxy]\n"
"enabled=yes\n";
static void echo_server(void) {
int srv = socket(AF_INET, SOCK_STREAM, 0);
if (srv < 0) _exit(1);
int opt = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(ECHO_PORT)};
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(srv, (struct sockaddr*)&addr, sizeof(addr)) < 0 || listen(srv, 1) < 0) { close(srv); _exit(1); }
int cli = accept(srv, NULL, NULL);
if (cli < 0) { close(srv); _exit(1); }
uint8_t buf[8192]; ssize_t n;
while ((n = recv(cli, buf, sizeof(buf), 0)) > 0) {
ssize_t sent = 0;
while (sent < n) { ssize_t s = send(cli, buf + sent, n - sent, 0); if (s < 0) goto done; sent += s; }
}
done: close(cli); close(srv);
}
static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* i = conn ? conn->instance : inst;
if (!i || !entry || entry->dgram == NULL || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t sid = 0; memcpy(&sid, entry->dgram + 2, 4);
uint64_t src = conn ? conn->peer_node_id : i->node_id;
if (subcmd == TCP_PROXY_SUBCMD_CONNECT && sid == stream_id) { remote_proxy_handle_connect(i, entry, sid, src); return; }
if (sid != stream_id) { queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) {
uint8_t status = (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) ? entry->dgram[TCP_PROXY_HDR_SIZE + 2] : 1;
connected_ok = (status == TCP_PROXY_CONNECTED_OK) ? 1 : -1;
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void monitor(void* arg) {
(void)arg;
if (g_done) { g_mon_id = NULL; return; }
static int phase = 0;
if (phase == 0) {
phase = 1;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, test_handler);
uint32_t ip = inet_addr("127.0.0.1");
uint16_t port = htons(ECHO_PORT);
uint8_t payload[6]; memcpy(payload, &ip, 4); memcpy(payload + 4, &port, 2);
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + 6);
e->dgram[0] = ETCP_ID_TCP_PROXY; e->dgram[1] = TCP_PROXY_SUBCMD_CONNECT;
memcpy(e->dgram + 2, &stream_id, 4);
memset(e->dgram + 6, 0, 2);
memcpy(e->dgram + TCP_PROXY_HDR_SIZE, payload, 6);
e->len = TCP_PROXY_HDR_SIZE + 6;
etcp_route_send(inst, inst->node_id, e);
}
}
if (phase == 1 && (connected_ok == 1 || connected_ok == -1)) {
if (connected_ok != 1) { printf("[FAIL] connect refused\n"); g_done = -1; return; }
// Get proxy conn, detach from uasync so we can use socket directly
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
if (!rc || rc->sock == SOCKET_INVALID) { printf("[FAIL] no proxy conn\n"); g_done = -1; return; }
uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL;
// Send data through OS socket directly
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
ssize_t n = send(rc->sock, send_buf, PAYLOAD_SIZE, MSG_NOSIGNAL);
if (n != PAYLOAD_SIZE) { printf("[FAIL] send %zd\n", n); g_done = -1; return; }
phase = 2;
}
if (phase == 2) {
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
if (rc && rc->sock != SOCKET_INVALID) {
ssize_t n = recv(rc->sock, recv_buf, PAYLOAD_SIZE, 0);
if (n > 0) {
if ((size_t)n == PAYLOAD_SIZE && memcmp(send_buf, recv_buf, PAYLOAD_SIZE) == 0) {
printf("[PASS] test_remote_proxy — %zd bytes echoed\n", n); g_ok = 1;
} else {
printf("[FAIL] echo mismatch: got %zd expected %d\n", n, PAYLOAD_SIZE);
}
g_done = 1; return;
}
}
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
}
static void timeout_cb(void* arg) {
(void)arg;
if (!g_done) { printf("[FAIL] timeout: conn=%d\n", connected_ok); g_done = -1; }
if (g_mon_id) { uasync_cancel_timeout(ua, g_mon_id); g_mon_id = NULL; }
}
int main(void) {
echo_pid = fork();
if (echo_pid == 0) { echo_server(); _exit(0); }
if (echo_pid < 0) { perror("fork"); return 1; }
usleep(100000);
if (test_mkdtemp(temp_dir) != 0) { kill(echo_pid, SIGTERM); waitpid(echo_pid,NULL,0); return 1; }
snprintf(cfg_path, sizeof(cfg_path), "%s/test.conf", temp_dir);
FILE* f = fopen(cfg_path, "w");
if (!f) { kill(echo_pid, SIGTERM); waitpid(echo_pid,NULL,0); test_rmdir(temp_dir); return 1; }
fprintf(f, "%s", cfg); fclose(f);
printf("=== test_remote_proxy ===\n");
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_categories(DEBUG_CATEGORY_ALL);
utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL));
ua = uasync_create();
inst = utun_instance_create(ua, cfg_path);
if (!inst) { printf("[FAIL] instance create\n"); goto done; }
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout_cb, "to");
while (!g_done) uasync_poll(ua, 100);
if (to_id) uasync_cancel_timeout(ua, to_id);
done:
if (g_mon_id) uasync_cancel_timeout(ua, g_mon_id);
if (inst) { inst->running = 0; utun_instance_destroy(inst); }
if (ua) uasync_destroy(ua, 0);
test_unlink(cfg_path); test_rmdir(temp_dir);
if (echo_pid) { kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); }
return g_ok ? 0 : 1;
}
+565
View File
@@ -0,0 +1,565 @@
// test_tcp_io.c — тесты библиотеки tcp_conn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "../lib/platform_compat.h"
#include "../lib/u_async.h"
#include "../lib/tcp_io.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static int tests_run = 0;
static int tests_passed = 0;
static int tests_failed = 0;
#define TEST_START(name) do { DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "TEST: %s... ", name); tests_run++; } while(0)
#define TEST_PASS() do { DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "PASS"); tests_passed++; } while(0)
#define TEST_FAIL(msg) do { DEBUG_ERROR(DEBUG_CATEGORY_UASYNC, "FAIL: %s", msg); tests_failed++; return; } while(0)
#define ASSERT_TRUE(cond, msg) do { if (!(cond)) TEST_FAIL(msg); } while(0)
#define ASSERT_EQ(a, b, msg) do { if ((a) != (b)) TEST_FAIL(msg); } while(0)
static int g_fin_count = 0;
static int g_error_count = 0;
static int g_last_error = 0;
static struct tcp_conn* g_last_fin_tc = NULL;
static int g_fin_sent_count = 0;
static int g_closed_count = 0;
static int g_last_event = 0; // 1=on_fin_sent, 2=on_closed — проверка порядка
static void on_fin(struct tcp_conn* tc, void* arg) {
(void)arg;
g_fin_count++;
g_last_fin_tc = tc;
}
static void on_error(struct tcp_conn* tc, int err, void* arg) {
(void)arg;
g_error_count++;
g_last_error = err;
}
static void on_fin_sent_cb(struct tcp_conn* tc, void* arg) {
(void)tc; (void)arg;
g_fin_sent_count++; g_last_event = 1;
}
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
(void)tc; (void)arg;
g_closed_count++; g_last_event = 2;
}
static void reset_counters(void) {
g_fin_count = 0;
g_error_count = 0;
g_last_error = 0;
g_last_fin_tc = NULL;
g_fin_sent_count = 0;
g_closed_count = 0;
g_last_event = 0;
}
static int push_write(struct tcp_conn* tc, const uint8_t* data, size_t len) {
size_t offset = 0;
while (offset < len) {
size_t chunk = len - offset;
if (chunk > tc->write_chunk_size) chunk = tc->write_chunk_size;
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
uint8_t* buf = memory_pool_alloc(tc->data_pool);
if (!e || !buf) { if (e) queue_entry_free(e); if (buf) memory_pool_free(tc->data_pool, buf); return -1; }
memcpy(buf, data + offset, chunk);
e->dgram = buf; e->len = (uint16_t)chunk;
queue_data_put(tc->write_queue, e);
offset += chunk;
}
return 0;
}
static void test_basic_send_recv(void) {
TEST_START("Basic send and recv via tcp_conn");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Отправляем данные через tcp_conn
uint8_t send_buf[3000];
memset(send_buf, 'A', sizeof(send_buf));
int ret = push_write(tc, send_buf, sizeof(send_buf));
ASSERT_EQ(ret, 0, "push_write first call failed");
uasync_poll(ua, 1); // write_cb должен отправить
// Читаем с другой стороны
uint8_t recv_buf[4096] = {0};
ssize_t total = 0;
while (total < (ssize_t)sizeof(send_buf)) {
ssize_t n = recv(sv[1], recv_buf + total, sizeof(recv_buf) - total, 0);
ASSERT_TRUE(n >= 0, "recv failed on peer socket");
total += n;
}
ASSERT_TRUE(memcmp(send_buf, recv_buf, sizeof(send_buf)) == 0, "received data mismatch");
// Отправляем с другой стороны — должно появиться в read_queue
uint8_t peer_data[500];
memset(peer_data, 'B', sizeof(peer_data));
ssize_t wret = send(sv[1], peer_data, sizeof(peer_data), MSG_NOSIGNAL);
ASSERT_TRUE(wret == sizeof(peer_data), "send on peer socket failed");
uasync_poll(ua, 10);
struct ll_entry* e = queue_data_get(tc->read_queue);
ASSERT_TRUE(e != NULL, "no data in read_queue");
ASSERT_EQ(e->len, (int)sizeof(peer_data), "read_queue entry length mismatch");
ASSERT_TRUE(memcmp(e->dgram, peer_data, sizeof(peer_data)) == 0, "read_queue data mismatch");
queue_entry_free(e);
queue_resume_callback(tc->read_queue);
// Закрываем сокет — должен вызвать on_fin
close(sv[1]);
uasync_poll(ua, 10);
ASSERT_EQ(g_fin_count, 1, "on_fin not called");
ASSERT_EQ(tc->fin_remote, 1, "tc->fin_remote not set");
tcp_conn_destroy(tc);
close(sv[0]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_partial_write(void) {
TEST_START("Partial write recovery");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 4096, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Заполняем приёмный буфер sv[1] маленькими чтениями, чтобы создать EAGAIN на sv[0]
// Отправляем много данных быстро, чтобы сокет заполнился
int send_size = 256;
uint8_t* big_buf = u_malloc(200000);
ASSERT_TRUE(big_buf != NULL, "malloc failed");
memset(big_buf, 'X', 200000);
int ret = push_write(tc, big_buf, 200000);
ASSERT_EQ(ret, 0, "push_write large failed");
uasync_poll(ua, 1); // write_cb отправляет что может
// Дрейним sv[1] и проверяем что все данные приходят
uint8_t read_buf[8192];
ssize_t got = 0;
int iterations = 0;
while (got < 200000 && iterations < 1000) {
uasync_poll(ua, 1); // write_cb может дослать остаток
ssize_t n = recv(sv[1], read_buf, sizeof(read_buf), 0);
if (n > 0) { got += n; iterations = 0; continue; }
iterations++;
}
ASSERT_TRUE(got == 200000, "not all data received");
u_free(big_buf);
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_high_water_pause(void) {
TEST_START("Read high-water pause and resume");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
// hw=2, lw=0 — пауза после 2 блоков, resume когда пусто
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 64, 8192, 2, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Шлём 5 блоков по 64 байта с другой стороны
for (int i = 0; i < 5; i++) {
uint8_t buf[64];
memset(buf, (uint8_t)i, sizeof(buf));
ssize_t wret = send(sv[1], buf, sizeof(buf), MSG_NOSIGNAL);
ASSERT_TRUE(wret == sizeof(buf), "send failed on iteration");
}
// Крутим poll пока не упрёмся в high_water (нужно несколько poll-итераций)
for (int i = 0; i < 20; i++) {
uasync_poll(ua, 1);
if (tc->read_paused) break;
}
ASSERT_TRUE(tc->read_paused == 1, "read not paused at high water");
// Дрейним read_queue — после lw=0 должен сработать resume
int drained = 0;
struct ll_entry* e;
while ((e = queue_data_get(tc->read_queue)) != NULL) {
drained++;
queue_entry_free(e);
queue_resume_callback(tc->read_queue);
}
ASSERT_TRUE(drained >= 2, "should have at least 2 entries");
uasync_poll(ua, 10);
ASSERT_TRUE(tc->read_queue->count > 0, "read not resumed");
// Дрейним остаток
while ((e = queue_data_get(tc->read_queue)) != NULL) {
queue_entry_free(e);
queue_resume_callback(tc->read_queue);
}
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_connect_detection(void) {
TEST_START("Connect completion detection");
#ifdef _WIN32
TEST_PASS();
return;
#else
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_TRUE(listen_fd >= 0, "socket failed");
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
ASSERT_EQ(bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)), 0, "bind failed");
socklen_t alen = sizeof(addr);
getsockname(listen_fd, (struct sockaddr*)&addr, &alen);
ASSERT_EQ(listen(listen_fd, 1), 0, "listen failed");
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_TRUE(client_fd >= 0, "socket failed");
fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, client_fd, 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
ASSERT_EQ(tc->connected, 0, "should not be connected yet");
int ret = connect(client_fd, (struct sockaddr*)&addr, sizeof(addr));
ASSERT_TRUE(ret < 0 && errno == EINPROGRESS, "connect should return EINPROGRESS");
int server_fd = accept(listen_fd, NULL, NULL);
ASSERT_TRUE(server_fd >= 0, "accept failed");
uasync_poll(ua, 100);
ASSERT_EQ(tc->connected, 1, "connect not detected");
close(server_fd);
tcp_conn_destroy(tc);
close(listen_fd);
uasync_destroy(ua, 0);
TEST_PASS();
#endif
}
static void test_error_callback(void) {
TEST_START("Error callback on broken socket");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
close(sv[1]);
uasync_poll(ua, 10);
ASSERT_EQ(tc->error, 1, "error not set on broken connection");
tcp_conn_destroy(tc);
close(sv[0]);
uasync_destroy(ua, 0);
TEST_PASS();
}
// ====================================================================
// FIN / Close через очередь — новые тесты
// ====================================================================
static void test_push_fin(void) {
TEST_START("push_fin via write_queue");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
uint8_t data[64];
memset(data, 'D', sizeof(data));
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed");
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin failed");
uasync_poll(ua, 10);
uint8_t recv_buf[128] = {0};
ssize_t total = 0;
while (total < (ssize_t)sizeof(data)) {
ssize_t n = recv(sv[1], recv_buf + total, sizeof(recv_buf) - total, 0);
ASSERT_TRUE(n >= 0, "recv failed on peer");
total += n;
}
ASSERT_EQ(total, (ssize_t)sizeof(data), "should receive all data before EOF");
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch");
// После всех данных — EOF (FIN)
ssize_t n = recv(sv[1], recv_buf, sizeof(recv_buf), 0);
ASSERT_EQ(n, 0, "should get EOF after FIN");
ASSERT_EQ(tc->fin_local, 1, "tc->fin_local not set");
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called");
ASSERT_EQ(g_closed_count, 0, "on_closed should not be called");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_push_close(void) {
TEST_START("push_close via write_queue");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_closed = on_closed_cb;
uint8_t data[64];
memset(data, 'E', sizeof(data));
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed");
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close failed");
uasync_poll(ua, 10);
// Peer должен получить данные перед закрытием
uint8_t recv_buf[128] = {0};
ssize_t total = recv(sv[1], recv_buf, sizeof(recv_buf), 0);
ASSERT_TRUE(total == sizeof(data), "peer should receive data");
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch");
ASSERT_EQ(tc->closed, 1, "tc->closed not set");
ASSERT_EQ(tc->sock, SOCKET_INVALID, "sock should be invalid after close");
ASSERT_EQ(g_closed_count, 1, "on_closed not called");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_fin_data_ordering(void) {
TEST_START("Data before FIN ordering");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
uint8_t data1[64], data2[32], data3[48];
memset(data1, 0x01, sizeof(data1)); memset(data2, 0x02, sizeof(data2)); memset(data3, 0x03, sizeof(data3));
ASSERT_EQ(push_write(tc, data1, sizeof(data1)), 0, "push_write data1");
ASSERT_EQ(push_write(tc, data2, sizeof(data2)), 0, "push_write data2");
ASSERT_EQ(push_write(tc, data3, sizeof(data3)), 0, "push_write data3");
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin");
uasync_poll(ua, 10);
// TCP — поток, блоки могут склеиться. Читаем всё и сверяем суммарно.
uint8_t buf[256], ref[sizeof(data1)+sizeof(data2)+sizeof(data3)];
memcpy(ref, data1, sizeof(data1)); memcpy(ref + sizeof(data1), data2, sizeof(data2)); memcpy(ref + sizeof(data1) + sizeof(data2), data3, sizeof(data3));
ssize_t total = 0;
while (total < (ssize_t)sizeof(ref)) {
ssize_t n = recv(sv[1], buf + total, sizeof(buf) - total, 0);
ASSERT_TRUE(n > 0, "recv failed on peer");
total += n;
}
ASSERT_EQ(total, (ssize_t)sizeof(ref), "total received mismatch");
ASSERT_TRUE(memcmp(buf, ref, sizeof(ref)) == 0, "data content mismatch");
// EOF
ssize_t n = recv(sv[1], buf, sizeof(buf), 0);
ASSERT_EQ(n, 0, "should get EOF after data");
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent should be called");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_double_push_fin(void) {
TEST_START("Double push_fin ignored");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "first push_fin");
ASSERT_EQ(tcp_conn_push_fin(tc), -1, "second push_fin should return -1");
uasync_poll(ua, 10);
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent called more than once");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_double_push_close(void) {
TEST_START("Double push_close ignored");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_closed = on_closed_cb;
ASSERT_EQ(tcp_conn_push_close(tc), 0, "first push_close");
uasync_poll(ua, 10);
ASSERT_EQ(g_closed_count, 1, "on_closed not called after first close");
ASSERT_EQ(tcp_conn_push_close(tc), -1, "second push_close should return -1");
ASSERT_EQ(g_closed_count, 1, "on_closed called more than once");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
static void test_fin_before_close(void) {
TEST_START("FIN before close ordering");
uasync_t* ua = uasync_create();
ASSERT_TRUE(ua != NULL, "uasync_create failed");
reset_counters();
int sv[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
tc->on_closed = on_closed_cb;
uint8_t data[32];
memset(data, 'F', sizeof(data));
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write");
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin");
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close");
uasync_poll(ua, 10);
// Peer получает данные
uint8_t buf[64];
ssize_t n = recv(sv[1], buf, sizeof(buf), 0);
ASSERT_EQ(n, (ssize_t)sizeof(data), "peer should get data");
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called");
ASSERT_EQ(g_closed_count, 1, "on_closed not called");
tcp_conn_destroy(tc);
close(sv[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_ALL);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "=== tcp_io Unit Tests ===");
test_basic_send_recv();
test_partial_write();
test_high_water_pause();
test_connect_detection();
test_error_callback();
test_push_fin();
test_push_close();
test_fin_data_ordering();
test_double_push_fin();
test_double_push_close();
test_fin_before_close();
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "=== Test Statistics ===");
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "Tests run: %d", tests_run);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "Tests passed: %d", tests_passed);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "Tests failed: %d", tests_failed);
return (tests_failed > 0) ? 1 : 0;
}
@@ -1,9 +1,9 @@
// test_tcp_proxy.c — TCP proxy smoke test: create/destroy without leaks
// test_tcp_proxy_client.c — TCP proxy client smoke test: create/destroy without leaks
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test_utils.h"
#include "../src/tcp_proxy.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../src/tun_if.h"
#include "../src/config_parser.h"
#include "../lib/u_async.h"
@@ -24,12 +24,12 @@ int main(void) {
{
struct UASYNC* ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
struct tcp_proxy_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999};
struct tcp_proxy_client_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999};
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0);
if (!p) { printf("[FAIL] tcp_proxy_create with mapping\n"); uasync_destroy(ua, 0); return 1; }
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0);
if (!p) { printf("[FAIL] tcp_proxy_client_create with mapping\n"); uasync_destroy(ua, 0); return 1; }
tcp_proxy_destroy(p);
tcp_proxy_client_destroy(p);
uasync_destroy(ua, 0);
check_no_leaks("create/destroy with mapping");
}
@@ -39,14 +39,14 @@ int main(void) {
struct UASYNC* ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0);
if (!p) { printf("[FAIL] tcp_proxy_create without mapping\n"); uasync_destroy(ua, 0); return 1; }
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0);
if (!p) { printf("[FAIL] tcp_proxy_client_create without mapping\n"); uasync_destroy(ua, 0); return 1; }
tcp_proxy_destroy(p);
tcp_proxy_client_destroy(p);
uasync_destroy(ua, 0);
check_no_leaks("create/destroy without mapping");
}
if (g_ok) printf("[PASS] test_tcp_proxy — create/destroy\n");
if (g_ok) printf("[PASS] test_tcp_proxy_client — create/destroy\n");
return g_ok ? 0 : 1;
}
+11 -11
View File
@@ -1,5 +1,5 @@
// test_tcp_proxy_remote.c — 2-node TCP 1MB forward via etcp_router
// Architecture: Client_A (active uIP) ↔ socketpair ↔ B (passive, etcp) ↔ Exit (remote_proxy) ↔ echo
// Architecture: Client_A (active uIP) ↔ socketpair ↔ B (passive, etcp) ↔ Exit (tcp_proxy_server) ↔ echo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,10 +16,10 @@
#include <errno.h>
#include <time.h>
#include "../src/tcp_proxy.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../src/etcp.h"
#include "../src/etcp_router.h"
#include "../src/remote_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/uip/uip.h"
@@ -36,7 +36,7 @@ static pid_t g_echo_pid;
static struct UTUN_INSTANCE* g_exit, *g_b;
static struct UASYNC* g_ua;
static int g_pair[2];
static struct tcp_proxy* g_proxy_b, *g_cli;
static struct tcp_proxy_client* g_proxy_b, *g_cli;
static int g_conn_idx, g_conn_up;
static struct uip_conn* g_uc;
static uint8_t *g_send_buf, *g_recv_buf;
@@ -75,12 +75,12 @@ done: close(cli); close(srv); _exit(0);
}
static void start_test(void) {
struct tcp_proxy_mapping_config m = {.local_port=9090,.remote_ip="127.0.0.1",.remote_port=g_echo_port};
g_proxy_b = tcp_proxy_create(g_b, g_ua, NULL, NULL, 0, 0, &m, 1, 0, g_pair[1], 0xCCCC000000000001ULL);
struct tcp_proxy_client_mapping_config m = {.local_port=9090,.remote_ip="127.0.0.1",.remote_port=g_echo_port};
g_proxy_b = tcp_proxy_client_create(g_b, g_ua, NULL, NULL, 0, 0, &m, 1, 0, g_pair[1], 0xCCCC000000000001ULL);
if (!g_proxy_b) { printf("[FAIL] proxy_b create\n"); g_done=-1; return; }
g_b->tcp_proxy = g_proxy_b;
g_cli = tcp_proxy_create(NULL, g_ua, NULL, NULL, 0, 0, NULL, 0, 0, g_pair[0], 0);
g_cli = tcp_proxy_client_create(NULL, g_ua, NULL, NULL, 0, 0, NULL, 0, 0, g_pair[0], 0);
if (!g_cli) { printf("[FAIL] cli create\n"); g_done=-1; return; }
g_conn_idx = tcp_proxy_active_open(g_cli, "10.99.0.100", 9090);
@@ -138,7 +138,7 @@ static const char* cfg_exit(int srv_port) { static char b[1024]; snprintf(b,size
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"tun_ip=10.99.0.1/24\ntun_ifname=tun99\n"
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n[remote_proxy]\nenabled=yes\n", srv_port); return b; }
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n[tcp_proxy_server]\nenabled=yes\n", srv_port); return b; }
static const char* cfg_b(int srv_port, int cli_port) { static char b[1024]; snprintf(b,sizeof(b),
"[global]\nmy_node_id=0xCCCC000000000002\n"
@@ -147,7 +147,7 @@ static const char* cfg_b(int srv_port, int cli_port) { static char b[1024]; snpr
"tun_ip=10.99.0.2/24\ntun_ifname=tun98\n"
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[client:c1]\nkeepalive=1\nlink=s1:127.0.0.1:%d\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"[remote_proxy]\nenabled=yes\n", srv_port, cli_port); return b; }
"[tcp_proxy_server]\nenabled=yes\n", srv_port, cli_port); return b; }
int main(void) {
printf("[SKIP] Active connections API removed, test skipped\n");
@@ -189,8 +189,8 @@ int main(void) {
done:
if (g_to_id) uasync_cancel_timeout(g_ua, g_to_id);
if (g_cli) tcp_proxy_destroy(g_cli);
if (g_proxy_b) tcp_proxy_destroy(g_proxy_b);
if (g_cli) tcp_proxy_client_destroy(g_cli);
if (g_proxy_b) tcp_proxy_client_destroy(g_proxy_b);
if (g_exit) { g_exit->running=0; utun_instance_destroy(g_exit); }
if (g_b) { g_b->running=0; utun_instance_destroy(g_b); }
if (g_ua) uasync_destroy(g_ua, 0);
+121
View File
@@ -0,0 +1,121 @@
// test_tcp_proxy_server.c — Test tcp_conn с реальным TCP соединением (echo сервер)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "../lib/tcp_io.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define ECHO_PORT 19991
#define TIMEOUT_MS 8000
#define PAYLOAD_SIZE 64
static int g_ok = 0, g_done = 0;
static pid_t echo_pid = 0;
static uint8_t send_buf[PAYLOAD_SIZE];
static void* timeout_id = NULL;
static void echo_server(void) {
int srv = socket(AF_INET, SOCK_STREAM, 0);
if (srv < 0) _exit(1);
int opt = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = htons(ECHO_PORT), .sin_addr.s_addr = inet_addr("127.0.0.1")};
if (bind(srv, (struct sockaddr*)&a, sizeof(a)) < 0 || listen(srv, 1) < 0) { close(srv); _exit(1); }
int cli = accept(srv, NULL, NULL);
if (cli < 0) { close(srv); _exit(1); }
uint8_t buf[8192]; ssize_t n;
while ((n = recv(cli, buf, sizeof(buf), 0)) > 0) {
ssize_t s = 0;
while (s < n) { ssize_t r = send(cli, buf + s, n - s, 0); if (r < 0) goto done; s += r; }
}
done: close(cli); close(srv);
}
static void on_fin_cb(struct tcp_conn* tc, void* arg) { (void)tc; (void)arg; }
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) { (void)tc; (void)arg; (void)err; }
static int push_write(struct tcp_conn* tc, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
uint8_t* buf = memory_pool_alloc(tc->data_pool);
if (!e || !buf) { if (e) queue_entry_free(e); if (buf) memory_pool_free(tc->data_pool, buf); return -1; }
memcpy(buf, data, len);
e->dgram = buf; e->len = (uint16_t)len;
queue_data_put(tc->write_queue, e);
return 0;
}
static void timeout_cb(void* arg) {
(void)arg;
printf("[FAIL] timeout\n");
g_done = -1;
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_WARN);
echo_pid = fork();
if (echo_pid == 0) { echo_server(); _exit(0); }
usleep(50000); // даём серверу запуститься
struct UASYNC* ua = uasync_create();
if (!ua) return 1;
timeout_id = uasync_set_timeout(ua, TIMEOUT_MS * 10, NULL, timeout_cb, "timeout");
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == SOCKET_INVALID) return 1;
socket_set_nonblocking(sock);
struct tcp_conn* tc = tcp_conn_create(ua, sock, 1500, 8192, 32, 8, on_fin_cb, on_error_cb, NULL);
if (!tc) return 1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(ECHO_PORT);
int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) { printf("[FAIL] connect: %s\n", strerror(errno)); g_done = -1; }
int phase = 0;
while (!g_done) {
uasync_poll(ua, 10);
if (phase == 0 && tc->connected) {
phase = 1;
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
push_write(tc, send_buf, PAYLOAD_SIZE);
}
if (phase == 1 && !tc->error) {
struct ll_entry* e = queue_data_get(tc->read_queue);
if (e) {
if (e->len == (uint16_t)PAYLOAD_SIZE && memcmp(send_buf, e->dgram, e->len) == 0) {
printf("[PASS] test_tcp_proxy_server — %u bytes echoed via tcp_conn\n", e->len); g_ok = 1;
} else {
printf("[FAIL] echo mismatch: got %u expected %d\n", e->len, PAYLOAD_SIZE);
}
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
queue_resume_callback(tc->read_queue);
g_done = 1;
}
}
}
tcp_conn_destroy(tc);
uasync_destroy(ua, 0);
kill(echo_pid, SIGKILL);
waitpid(echo_pid, NULL, 0);
return g_ok ? 0 : 1;
}
+102
View File
@@ -531,6 +531,107 @@ static void test_concurrent_operations(void) {
TEST_PASS();
}
/* Test 8: Dynamic socket read/write monitoring toggle */
static void test_socket_flags_toggle(void) {
TEST_START("Dynamic socket read/write monitoring toggle");
uasync_t* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync instance");
int sockets[2];
#ifdef _WIN32
sockets[0] = socket(AF_INET, SOCK_DGRAM, 0);
sockets[1] = socket(AF_INET, SOCK_DGRAM, 0);
ASSERT_TRUE(sockets[0] >= 0 && sockets[1] >= 0, "Failed to create sockets");
struct sockaddr_in addr1, addr2;
memset(&addr1, 0, sizeof(addr1));
addr1.sin_family = AF_INET;
addr1.sin_addr.s_addr = inet_addr("127.0.0.1");
addr1.sin_port = htons(0);
memset(&addr2, 0, sizeof(addr2));
addr2.sin_family = AF_INET;
addr2.sin_addr.s_addr = inet_addr("127.0.0.1");
addr2.sin_port = htons(0);
ASSERT_EQ(bind(sockets[0], (struct sockaddr*)&addr1, sizeof(addr1)), 0, "Failed to bind socket 0");
ASSERT_EQ(bind(sockets[1], (struct sockaddr*)&addr2, sizeof(addr2)), 0, "Failed to bind socket 1");
ASSERT_EQ(connect(sockets[0], (struct sockaddr*)&addr2, sizeof(addr2)), 0, "connect 0->1");
ASSERT_EQ(connect(sockets[1], (struct sockaddr*)&addr1, sizeof(addr1)), 0, "connect 1->0");
#else
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), 0, "Failed to create socket pair");
#endif
for (int i = 0; i < 2; i++) {
int flags = fcntl(sockets[i], F_GETFL, 0);
fcntl(sockets[i], F_SETFL, flags | O_NONBLOCK);
}
test_context_t read_ctx = {0};
test_context_t write_ctx = {0};
void* s_id = uasync_add_socket(ua, sockets[0], test_socket_callback, NULL, NULL, &read_ctx);
ASSERT_NOT_NULL(s_id, "Failed to add socket");
/* Write data — read callback should fire on sockets[0] */
char data = 'x';
ssize_t wret = write(sockets[1], &data, 1);
ASSERT_TRUE(wret == 1, "Write should succeed");
uasync_poll(ua, 1);
ASSERT_TRUE(read_ctx.callback_count > 0, "Read callback should fire after write");
/* Disable read monitoring */
err_t ret = uasync_set_socket_read(ua, s_id, 0);
ASSERT_EQ(ret, ERR_OK, "uasync_set_socket_read(0) should succeed");
/* Write again — read callback should NOT fire because read monitoring is disabled */
int before_count = read_ctx.callback_count;
wret = write(sockets[1], &data, 1);
ASSERT_TRUE(wret == 1, "Write should succeed");
uasync_poll(ua, 1);
ASSERT_TRUE(read_ctx.callback_count == before_count, "Read callback should NOT fire when disabled");
/* Enable read monitoring back */
ret = uasync_set_socket_read(ua, s_id, 1);
ASSERT_EQ(ret, ERR_OK, "uasync_set_socket_read(1) should succeed");
uasync_poll(ua, 1);
ASSERT_TRUE(read_ctx.callback_count > before_count, "Read callback should fire after re-enable");
/* Test write flag toggle — socket is always writable (newly created), so write callback fires */
void* s_id2 = uasync_add_socket(ua, sockets[1], NULL, test_socket_callback, NULL, &write_ctx);
ASSERT_NOT_NULL(s_id2, "Failed to add socket for write test");
uasync_poll(ua, 1);
ASSERT_TRUE(write_ctx.callback_count > 0, "Write callback should fire for writable socket");
/* Disable write monitoring */
ret = uasync_set_socket_write(ua, s_id2, 0);
ASSERT_EQ(ret, ERR_OK, "uasync_set_socket_write(0) should succeed");
int before_write = write_ctx.callback_count;
uasync_poll(ua, 1);
ASSERT_TRUE(write_ctx.callback_count == before_write, "Write callback should NOT fire when disabled");
/* Enable write monitoring back */
ret = uasync_set_socket_write(ua, s_id2, 1);
ASSERT_EQ(ret, ERR_OK, "uasync_set_socket_write(1) should succeed");
uasync_poll(ua, 1);
ASSERT_TRUE(write_ctx.callback_count > before_write, "Write callback should fire after re-enable");
/* Test invalid parameters */
ret = uasync_set_socket_read(ua, NULL, 1);
ASSERT_EQ(ret, ERR_FAIL, "set_socket_read with NULL s_id should fail");
ret = uasync_set_socket_write(NULL, s_id, 1);
ASSERT_EQ(ret, ERR_FAIL, "set_socket_write with NULL ua should fail");
/* Cleanup */
uasync_remove_socket(ua, s_id);
uasync_remove_socket(ua, s_id2);
close(sockets[0]);
close(sockets[1]);
uasync_destroy(ua, 0);
TEST_PASS();
}
/* Main test runner */
int main(void) {
debug_config_init();
@@ -549,6 +650,7 @@ int main(void) {
test_socket_management();
test_error_handling();
test_concurrent_operations();
test_socket_flags_toggle();
/* Print statistics */
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "=== Test Statistics ===");
+6 -6
View File
@@ -1,5 +1,5 @@
// test_udp_proxy.c — 2-node UDP echo test via etcp_router
// Node1 (client): tcp_proxy → sends UDP_REQUEST to exit
// Node1 (client): tcp_proxy_client → sends UDP_REQUEST to exit
// Node2 (exit): receives → creates UDP socket → echoes → sends UDP_REPLY
#include <stdio.h>
#include <stdlib.h>
@@ -19,8 +19,8 @@
#include "../src/etcp_connections.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/udp_proxy.h"
#include "../src/remote_proxy.h"
#include "../src/proxy/udp_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@@ -47,7 +47,7 @@ static const char* cfg_node_client(void) {
"[server: s1]\naddr=127.0.0.1:9071\ntype=public\n"
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9072\n"
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"[tcp_proxy]\n"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
@@ -66,7 +66,7 @@ static const char* cfg_node_exit(void) {
"tun_ifname=tun98\n"
"[server: s1]\naddr=127.0.0.1:9072\ntype=public\n"
"[allowed_keys]\nallow_all=1\n"
"[remote_proxy]\nenabled=yes\n");
"[tcp_proxy_server]\nenabled=yes\n");
return buf;
}
@@ -126,7 +126,7 @@ static void monitor(void* arg) {
if (l->initialized && c->crypto_ctx.initialized) exit_ok = 1;
if (cli_ok && exit_ok) {
g_test_phase = 1;
// Bind client handler for UDP replies (overrides what tcp_proxy_create set, for test verification)
// Bind client handler for UDP replies (overrides what tcp_proxy_client_create set, for test verification)
etcp_router_bind(cli, ETCP_ID_UDP_PROXY, cli_recv_cb);
// Send UDP_REQUEST
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
+77 -45
View File
@@ -602,6 +602,31 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
}
break;
}
case ETCPMON_RSP_DEBUG_CONFIG: {
if (payload_size >= 2) {
client->debug_global_level = payload[0];
client->debug_category_count = payload[1];
uint16_t names_len = (uint16_t)strlen((const char*)(payload + 2)) + 1;
if (2 + names_len <= payload_size) {
size_t copy_len = names_len < sizeof(client->debug_category_names) ? names_len : sizeof(client->debug_category_names) - 1;
memcpy(client->debug_category_names, payload + 2, copy_len);
client->debug_category_names[copy_len] = '\0';
uint16_t levels_off = 2 + names_len;
uint8_t lvl_count = client->debug_category_count;
if (lvl_count > ETCPMON_MAX_DEBUG_CATEGORIES) lvl_count = ETCPMON_MAX_DEBUG_CATEGORIES;
for (uint8_t i = 0; i < lvl_count && levels_off + i < payload_size; i++)
client->debug_category_levels[i] = payload[levels_off + i];
client->debug_config_dirty = 1;
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] RSP_DEBUG_CONFIG global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), client->debug_global_level, client->debug_category_count);
fflush(client->log_file);
}
}
}
break;
}
}
}
@@ -666,7 +691,7 @@ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t
/* === НОВОЕ: ВСЕГДА вызываем legacy обработчики (on_conn_list / on_metrics / on_error) ===
* Это позволяет использовать и старый код, и новый асинхронный send_request одновременно */
if (seq_id == 0 || msg_type == ETCPMON_RSP_CONN_LIST ||
msg_type == ETCPMON_RSP_SOCKET_LIST ||
msg_type == ETCPMON_RSP_SOCKET_LIST || msg_type == ETCPMON_RSP_DEBUG_CONFIG ||
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR) {
handle_response(client, msg_type, payload, payload_size);
}
@@ -859,59 +884,66 @@ struct metrics_history* etcpmon_client_get_history(struct etcpmon_client* client
return &client->history;
}
static void debug_config_auto_callback(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) {
(void)arg; (void)msg_type; (void)payload; (void)payload_size;
}
int etcpmon_client_request_debug_config(struct etcpmon_client* client) {
if (!client || client->sock == INVALID_SOCKET) return -1;
return etcpmon_client_send_request(client, ETCPMON_CMD_GET_DEBUG_CONFIG, NULL, 0,
debug_config_auto_callback, NULL);
}
int etcpmon_client_send_debug_config(struct etcpmon_client* client,
uint8_t global_level, uint8_t count, const uint8_t* levels) {
if (!client || client->sock == INVALID_SOCKET || !levels || count == 0 || count > ETCPMON_MAX_DEBUG_CATEGORIES) return -1;
uint8_t payload[2 + ETCPMON_MAX_DEBUG_CATEGORIES];
payload[0] = global_level;
payload[1] = count;
memcpy(payload + 2, levels, count);
uint16_t payload_size = 2 + count;
struct etcpmon_msg_header hdr;
etcpmon_build_header(&hdr, payload_size, ETCPMON_CMD_SET_DEBUG_CONFIG, 0);
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] CMD_SET_DEBUG_CONFIG global=%d count=%d\n",
(unsigned long long)get_timestamp_ms(), global_level, count);
fflush(client->log_file);
}
int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0);
if (sent != sizeof(hdr)) return -1;
sent = send(client->sock, (const char*)payload, payload_size, 0);
if (sent != payload_size) return -1;
client->debug_global_level = global_level;
for (uint8_t i = 0; i < count; i++) client->debug_category_levels[i] = levels[i];
return 0;
}
void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon_rsp_metrics* metrics) {
if (!client || !metrics) return;
struct metrics_history* h = &client->history;
int idx = h->head;
// for (int m = 0; m < GRAPH_METRICS_COUNT; m++) {
// float v = h->values[m][idx];
// if (v < h->min_val[m]) h->min_val[m] = v;
// if (v > h->max_val[m]) h->max_val[m] = v;
// }
if (client->log_file) {
fprintf(client->log_file, "add_to_history: rtt_last=%u rtt_avg10=%u rtt_avg100=%u jitter=%u\n",
metrics->etcp.rtt_last, metrics->etcp.rtt_avg_10, metrics->etcp.rtt_avg_100, metrics->etcp.jitter);
fflush(client->log_file);
/* BBR per-link metrics (use first link as representative) */
if (client->last_links_count > 0 && client->last_links) {
struct etcpmon_link_metrics* l = &client->last_links[0];
h->values[GRAPH_METRIC_INFLIGHT_BYTES][idx] = l->inflight_bytes / 1024.f;
h->values[GRAPH_METRIC_INFLIGHT_LIM][idx] = l->inflight_lim_bytes / 1024.f;
h->values[GRAPH_METRIC_MIN_RTT][idx] = l->bbr_min_rtt_us / 1000.f;
h->values[GRAPH_METRIC_BW_HI][idx] = l->bbr_bw_hi / 125.f;
h->values[GRAPH_METRIC_PACING_RATE][idx] = l->bbr_pacing_rate / 125.f;
h->values[GRAPH_METRIC_BW_LO][idx] = l->bbr_bw_lo / 125.f;
h->values[GRAPH_METRIC_INFLIGHT_HI][idx] = l->bbr_inflight_hi / 1024.f;
h->values[GRAPH_METRIC_BBR_MODE][idx] = (float)l->bbr_mode;
}
float scale = 0.1f; // to ms (protocol: 0.1ms units)
h->values[GRAPH_METRIC_RTT_LAST][idx] = (float)metrics->etcp.rtt_last * scale;
h->values[GRAPH_METRIC_RTT_AVG10][idx] = (float)metrics->etcp.rtt_avg_10 * scale;
h->values[GRAPH_METRIC_RTT_AVG100][idx] = (float)metrics->etcp.rtt_avg_100 * scale;
h->values[GRAPH_METRIC_JITTER][idx] = (float)metrics->etcp.jitter * scale;
uint32_t retrans_delta = 0;
uint32_t acks_delta = 0;
uint64_t bytes_delta = 0;
if (h->count > 0) {
if (metrics->etcp.retrans_count >= h->last_retrans) {
retrans_delta = metrics->etcp.retrans_count - h->last_retrans;
}
if (metrics->etcp.ack_count >= h->last_acks) {
acks_delta = metrics->etcp.ack_count - h->last_acks;
}
if (metrics->etcp.bytes_sent_total >= h->last_bytes_sent) {
bytes_delta = metrics->etcp.bytes_sent_total - h->last_bytes_sent;
}
}
h->last_retrans = metrics->etcp.retrans_count;
h->last_acks = metrics->etcp.ack_count;
h->last_bytes_sent = metrics->etcp.bytes_sent_total;
h->values[GRAPH_METRIC_RETRANS][idx] = (float)retrans_delta;
h->values[GRAPH_METRIC_ACKS][idx] = (float)acks_delta;
h->values[GRAPH_METRIC_INFLIGHT][idx] = (float)metrics->etcp.unacked_bytes;
h->values[GRAPH_METRIC_BYTES_SENT][idx] = (float)bytes_delta;
h->head = (h->head + 1) % GRAPH_HISTORY_SIZE;
if (h->count < GRAPH_HISTORY_SIZE) {
h->count++;
}
if (h->count < GRAPH_HISTORY_SIZE) h->count++;
if (client->log_file) {
fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head);
fflush(client->log_file);
+18 -8
View File
@@ -21,14 +21,14 @@ extern "C" {
/* Graph metrics types */
typedef enum {
GRAPH_METRIC_RTT_LAST = 0,
GRAPH_METRIC_RTT_AVG10,
GRAPH_METRIC_RTT_AVG100,
GRAPH_METRIC_JITTER,
GRAPH_METRIC_RETRANS,
GRAPH_METRIC_ACKS,
GRAPH_METRIC_INFLIGHT,
GRAPH_METRIC_BYTES_SENT,
GRAPH_METRIC_INFLIGHT_BYTES = 0,
GRAPH_METRIC_INFLIGHT_LIM,
GRAPH_METRIC_MIN_RTT,
GRAPH_METRIC_BW_HI,
GRAPH_METRIC_PACING_RATE,
GRAPH_METRIC_BW_LO,
GRAPH_METRIC_INFLIGHT_HI,
GRAPH_METRIC_BBR_MODE,
GRAPH_METRIC_COUNT
} graph_metric_type_t;
@@ -89,6 +89,13 @@ struct etcpmon_client {
/* Last received response seq_id (for matching) */
uint8_t last_response_seq_id;
/* Debug config cache */
uint8_t debug_global_level;
uint8_t debug_category_count;
uint8_t debug_category_levels[ETCPMON_MAX_DEBUG_CATEGORIES];
char debug_category_names[512];
uint8_t debug_config_dirty;
/* Callbacks */
void (*on_connected)(void* user_data);
void (*on_disconnected)(void* user_data);
@@ -189,6 +196,9 @@ void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon
/* Send action command to server */
int etcpmon_client_send_action(struct etcpmon_client* client, const char* action);
int etcpmon_client_request_debug_config(struct etcpmon_client* client);
int etcpmon_client_send_debug_config(struct etcpmon_client* client, uint8_t global_level, uint8_t count, const uint8_t* levels);
uint64_t get_timestamp_ms(void); // Add this declaration
#ifdef __cplusplus
+18 -14
View File
@@ -15,12 +15,12 @@
#include <stdio.h>
#include "etcpmon_gui.h"
const COLORREF graph_colors[GRAPH_METRICS_COUNT] = {
RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0),
RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255)
RGB(255,0,0), RGB(0,200,0), RGB(0,128,255), RGB(255,128,0),
RGB(160,0,200), RGB(0,180,180), RGB(255,0,128), RGB(200,200,0)
};
const char* graph_metric_names[GRAPH_METRICS_COUNT] = {
"RTT Last","RTT Avg10","RTT Avg100","Jitter",
"Retrns","ACKs","Inflght","Bytes/s"
"InflB","Cwnd","minRTT","bwHi",
"Pace","bwLo","infHi","Mode"
};
static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest)
{
@@ -165,17 +165,21 @@ void UpdateChannelValues(struct etcpmon_app* app, int history_idx)
if (!app->hChannelValue[i]) continue;
float val = h->values[i][history_idx];
char buf[32] = {0};
if (i == GRAPH_METRIC_BYTES_SENT) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.));
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B/s", val);
} else if (i == GRAPH_METRIC_INFLIGHT) {
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) {
snprintf(buf, sizeof(buf), "%.0f /s", val);
} else {
if (i == GRAPH_METRIC_BW_HI || i == GRAPH_METRIC_BW_LO || i == GRAPH_METRIC_PACING_RATE) {
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/1024.);
else snprintf(buf, sizeof(buf), "%.1f KB/s", val);
} else if (i == GRAPH_METRIC_INFLIGHT_BYTES || i == GRAPH_METRIC_INFLIGHT_LIM || i == GRAPH_METRIC_INFLIGHT_HI) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB", val/(1024.*1024.));
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_MIN_RTT) {
snprintf(buf, sizeof(buf), "%.1f ms", val);
} else if (i == GRAPH_METRIC_BBR_MODE) {
static const char* mnames[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"};
int m = (int)val; if (m < 0) m = 0; if (m > 3) m = 3;
snprintf(buf, sizeof(buf), "%s", mnames[m]);
} else {
snprintf(buf, sizeof(buf), "%.1f", val);
}
if (strcmp(buf, last_text[i]) == 0)
continue;
+160 -19
View File
@@ -20,7 +20,8 @@
//#pragma comment(lib, "user32.lib")
//#pragma comment(lib, "gdi32.lib")
#define WINDOW_WIDTH 1000
#define WINDOW_HEIGHT 1250
#define WINDOW_HEIGHT 1300
#define WINDOW_CLIENT_W 1250
#define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */
/* Global app pointer for callbacks */
static struct etcpmon_app* g_app = NULL;
@@ -33,6 +34,8 @@ static void OnConnectionSelect(struct etcpmon_app* app);
static void OnAction(struct etcpmon_app* app);
static void OnTimer(struct etcpmon_app* app);
static void UpdateUIState(struct etcpmon_app* app);
static void OnDebugRadio(struct etcpmon_app* app, int control_id);
static void RefreshDebugUI(struct etcpmon_app* app);
static void AddTooltip(HWND hTip, HWND hCtrl, const char* text);
static void CreateTooltips(struct etcpmon_app* app);
/* Callback functions for client events */
@@ -113,7 +116,7 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
"ETCP Monitor",
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
CW_USEDEFAULT, CW_USEDEFAULT,
WINDOW_WIDTH, WINDOW_HEIGHT,
WINDOW_CLIENT_W, WINDOW_HEIGHT,
NULL, NULL, app->hInstance, app
);
@@ -239,8 +242,8 @@ static void CreateControls(struct etcpmon_app* app) {
y += GRAPH_HEIGHT + 8;
const char* channel_short_names[] = {
"RTT-L", "RTT10", "RTT100", "Jitter",
"Retrns", "ACKs", "Inflght", "Bytes/s"
"InflB", "Cwnd", "minRTT", "bwHi",
"Pace", "bwLo", "infHi", "Mode"
};
int block_w = 200;
@@ -724,6 +727,62 @@ static void CreateControls(struct etcpmon_app* app) {
q_col1 + 85 + i * 65, qy - 2, 60, 18, hWnd, (HMENU)((UINT_PTR)(IDC_EDIT_DEBUG_0 + i)), hInst, NULL);
}
/* ========================================================================
* Debug Levels section — right column
* ======================================================================== */
{
int dy = 485;
app->hDebugGroupBox = CreateWindowExA(0, "BUTTON", "Debug Levels",
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
1005, dy, 240, 545, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
dy += 20;
const char* header_texts[6] = {
"N\r\nO\r\nN\r\nE", "E\r\nR\r\nR\r\nO\r\nR", "W\r\nA\r\nR\r\nN",
"I\r\nN\r\nF\r\nO", "D\r\nE\r\nB\r\nU\r\nG", "T\r\nR\r\nA\r\nC\r\nE"
};
static const char* cat_names[] = {
"UASYNC","LL_QUEUE","CONNECTION","ETCP","CRYPTO",
"MEMORY","TIMING","CONFIG","TUN","ROUTING",
"TIMERS","NORMALIZER","BGP","SOCKET","CONTROL",
"DUMP","TRAFFIC","DEBUG_CAT","GENERAL","NAT","KEEPALIVE","ETCPROUTE"
};
int ncat = sizeof(cat_names) / sizeof(cat_names[0]);
app->debug_cat_count = ncat;
CreateWindowExA(0, "STATIC", "Global:", WS_CHILD | WS_VISIBLE,
1015, dy + 2, 55, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 1100 + l * 22;
DWORD style = WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON;
if (l == 0) style |= WS_GROUP;
app->hDebugGlobalRadio[l] = CreateWindowExA(0, "BUTTON", "",
style, lx, dy, 20, 20, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_GLOBAL_BASE + l), hInst, NULL);
}
dy += 22;
for (int l = 0; l < 6; l++) {
int lx = 1100 + l * 22;
app->hDebugHeaderEdit[l] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", header_texts[l],
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER | ES_MULTILINE,
lx, dy, 22, 60, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_HEADER_BASE + l), hInst, NULL);
}
dy += 65;
for (int r = 0; r < ncat; r++) {
app->hDebugCatLabel[r] = CreateWindowExA(0, "STATIC", cat_names[r],
WS_CHILD | WS_VISIBLE | SS_LEFT | SS_CENTERIMAGE,
1015, dy + 1, 80, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_NAME_BASE + r), hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 1100 + l * 22;
DWORD style = WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON;
if (l == 0) style |= WS_GROUP;
app->hDebugCatRadio[r][l] = CreateWindowExA(0, "BUTTON", "",
style, lx, dy, 20, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_ROW_BASE + r * 8 + l), hInst, NULL);
}
dy += 18;
}
}
}
static void AddTooltip(HWND hTip, HWND hCtrl, const char* text)
{
@@ -942,21 +1001,27 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
switch (message) {
case WM_COMMAND:
if (app) {
switch (LOWORD(wParam)) {
case IDC_BTN_CONNECT:
OnConnect(app);
break;
case IDC_BTN_DISCONNECT:
OnDisconnect(app);
break;
case IDC_LIST_CONNECTIONS:
if (HIWORD(wParam) == LBN_SELCHANGE) {
OnConnectionSelect(app);
}
break;
case IDC_BTN_ACTION:
OnAction(app);
break;
WORD ctrl_id = LOWORD(wParam);
if ((ctrl_id >= IDC_DEBUG_GLOBAL_BASE && ctrl_id <= IDC_DEBUG_GLOBAL_BASE + 5) ||
(ctrl_id >= IDC_DEBUG_ROW_BASE && ctrl_id < IDC_DEBUG_ROW_BASE + IDC_DEBUG_MAX_CAT * 8)) {
OnDebugRadio(app, ctrl_id);
} else {
switch (ctrl_id) {
case IDC_BTN_CONNECT:
OnConnect(app);
break;
case IDC_BTN_DISCONNECT:
OnDisconnect(app);
break;
case IDC_LIST_CONNECTIONS:
if (HIWORD(wParam) == LBN_SELCHANGE) {
OnConnectionSelect(app);
}
break;
case IDC_BTN_ACTION:
OnAction(app);
break;
}
}
}
break;
@@ -1049,6 +1114,57 @@ static void OnAction(struct etcpmon_app* app) {
}
}
static void OnDebugRadio(struct etcpmon_app* app, int control_id) {
static int in_handler = 0;
if (in_handler) return;
if (!app || !app->client) return;
in_handler = 1;
if (control_id >= IDC_DEBUG_GLOBAL_BASE && control_id <= IDC_DEBUG_GLOBAL_BASE + 5) {
int level = control_id - IDC_DEBUG_GLOBAL_BASE;
for (int l = 0; l <= 5; l++)
SendMessage(app->hDebugGlobalRadio[l], BM_SETCHECK, (l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
app->client->debug_global_level = (uint8_t)level;
} else if (control_id >= IDC_DEBUG_ROW_BASE) {
int flat = control_id - IDC_DEBUG_ROW_BASE;
int row = flat / 8;
int level = flat % 8;
if (row >= app->debug_cat_count || level > 5) { in_handler = 0; return; }
for (int l = 0; l <= 5; l++) {
if (app->hDebugCatRadio[row][l])
SendMessage(app->hDebugCatRadio[row][l], BM_SETCHECK, (l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
app->client->debug_category_levels[row] = (uint8_t)level;
}
etcpmon_client_send_debug_config(app->client, app->client->debug_global_level,
app->debug_cat_count, app->client->debug_category_levels);
in_handler = 0;
}
static void RefreshDebugUI(struct etcpmon_app* app) {
if (!app || !app->client) return;
struct etcpmon_client* cl = app->client;
for (int l = 0; l <= 5; l++) {
if (app->hDebugGlobalRadio[l])
SendMessage(app->hDebugGlobalRadio[l], BM_SETCHECK,
(l == cl->debug_global_level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
int n = cl->debug_category_count;
if (n > app->debug_cat_count) n = app->debug_cat_count;
for (int r = 0; r < n; r++) {
int level = cl->debug_category_levels[r];
if (level > 5) level = 0;
for (int l = 0; l <= 5; l++) {
if (app->hDebugCatRadio[r][l])
SendMessage(app->hDebugCatRadio[r][l], BM_SETCHECK,
(l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
}
}
static void OnTimer(struct etcpmon_app* app) {
if (!app || !app->client) return;
@@ -1064,9 +1180,15 @@ static void OnTimer(struct etcpmon_app* app) {
if (app->need_initial_request && app->isConnected) {
etcpmon_client_request_list(app->client);
etcpmon_client_request_socket_list(app->client);
etcpmon_client_request_debug_config(app->client);
app->need_initial_request = 0;
}
if (app->isConnected && app->client->debug_config_dirty) {
RefreshDebugUI(app);
app->client->debug_config_dirty = 0;
}
}
static void UpdateUIState(struct etcpmon_app* app) {
@@ -1436,8 +1558,27 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
links[i].inflight_lim_bytes,
links[i].inflight_packets);
char line3[320];
const char* mode_names[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"};
const char* cycle_names[] = {"UP","DOWN","CRUISE","REFILL"};
snprintf(line3, sizeof(line3),
" BBR: mode=%s cycle=%s full_bw=%s loss=%s pace=%.1f KB/s rtt=%u us "
"bwHi=%.1f KB/s bwLo=%.1f KB/s infHi=%u lo=%u gain=%.2f",
mode_names[links[i].bbr_mode > 3 ? 0 : links[i].bbr_mode],
cycle_names[links[i].bbr_cycle_idx > 3 ? 0 : links[i].bbr_cycle_idx],
links[i].bbr_full_bw_reached ? "yes" : "no",
links[i].bbr_loss_in_round ? "yes" : "no",
(float)links[i].bbr_pacing_rate / 125.0f,
links[i].bbr_min_rtt_us,
(float)links[i].bbr_bw_hi / 125.0f,
(float)links[i].bbr_bw_lo / 125.0f,
links[i].bbr_inflight_hi,
links[i].bbr_inflight_lo,
(float)links[i].bbr_pacing_gain / 256.0f);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line1);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line3);
}
InvalidateRect(app->hListLinks, NULL, FALSE);
}
+15
View File
@@ -156,6 +156,13 @@ extern "C" {
#define IDC_EDIT_ACTIVE_TIMEOUTS 700
#define IDC_EDIT_BUSY_MEMORY 701
/* Debug Levels control IDs */
#define IDC_DEBUG_MAX_CAT 64
#define IDC_DEBUG_GLOBAL_BASE 1000 /* +0..5 radio: NONE(0)..TRACE(5) */
#define IDC_DEBUG_HEADER_BASE 1010 /* +0..5 header EDIT */
#define IDC_DEBUG_ROW_BASE 1100 /* + row*8 + level */
#define IDC_DEBUG_NAME_BASE 2000 /* + row: category name label */
/* Graph control ID */
#define IDC_GRAPH 500
@@ -315,6 +322,14 @@ struct etcpmon_app {
int graph_cursor_y;
int graph_cursor_active;
/* Debug Levels controls */
HWND hDebugGroupBox;
HWND hDebugGlobalRadio[6]; /* 0=NONE..5=TRACE */
HWND hDebugHeaderEdit[6]; /* вертикальный текст: T\nR\nA\nC\nE итд */
HWND hDebugCatLabel[IDC_DEBUG_MAX_CAT]; /* метки категорий */
HWND hDebugCatRadio[IDC_DEBUG_MAX_CAT][6]; /* [row][level] радио-кнопки */
int debug_cat_count;
/* Client state */
struct etcpmon_client* client;
+33
View File
@@ -35,6 +35,7 @@ extern "C" {
#define ETCPMON_MAX_CONN_NAME 32
#define ETCPMON_MAX_CONNECTIONS 250
#define ETCPMON_MAX_LINKS 16
#define ETCPMON_MAX_DEBUG_CATEGORIES 64
/* Update interval in milliseconds */
#define ETCPMON_UPDATE_INTERVAL_MS 100
@@ -50,11 +51,14 @@ extern "C" {
#define ETCPMON_CMD_DISCONNECT 0x04 /* Disconnect from monitoring */
#define ETCPMON_CMD_LIST_SOCKETS 0x05 /* Request local socket list */
#define ETCPMON_CMD_ACTION 0x06 /* Execute action (text command) */
#define ETCPMON_CMD_GET_DEBUG_CONFIG 0x07 /* Request current debug levels */
#define ETCPMON_CMD_SET_DEBUG_CONFIG 0x08 /* Set debug levels (byte array) */
/* Server -> Client responses */
#define ETCPMON_RSP_CONN_LIST 0x81 /* Connection list response */
#define ETCPMON_RSP_METRICS 0x82 /* Metrics response */
#define ETCPMON_RSP_SOCKET_LIST 0x83 /* Socket list response */
#define ETCPMON_RSP_DEBUG_CONFIG 0x84 /* Debug levels response */
#define ETCPMON_RSP_ERROR 0xFF /* Error response */
/* Error codes for RSP_ERROR */
@@ -237,6 +241,20 @@ struct etcpmon_link_metrics {
uint32_t inflight_bytes;
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
/* BBR congestion control metrics */
uint8_t bbr_mode; /* 0=STARTUP, 1=DRAIN, 2=PROBE_BW, 3=PROBE_RTT */
uint8_t bbr_cycle_idx; /* 0=UP, 1=DOWN, 2=CRUISE, 3=REFILL */
uint8_t bbr_full_bw_reached; /* startup completed? 0/1 */
uint8_t bbr_loss_in_round; /* loss in current round? 0/1 */
uint32_t bbr_pacing_rate; /* bytes/sec */
uint32_t bbr_min_rtt_us; /* min RTT (microseconds) */
uint32_t bbr_bw_hi; /* max bandwidth estimate (bytes/sec, converted from BW_UNIT scale) */
uint32_t bbr_bw_lo; /* conservative bandwidth estimate (bytes/sec) */
uint32_t bbr_inflight_hi; /* upper inflight bound (bytes) */
uint32_t bbr_inflight_lo; /* lower inflight bound (bytes) */
uint16_t bbr_pacing_gain; /* pacing gain (BBR_UNIT fixed-point: 710=2.77x, 192=0.75x) */
uint16_t pad_bbr; /* padding for 4-byte alignment */
};
/* TUN interface metrics */
@@ -272,6 +290,21 @@ struct etcpmon_rsp_metrics {
/* Followed by etcp.links_count * struct etcpmon_link_metrics */
};
/* RSP_DEBUG_CONFIG: Server -> Client debug levels */
struct etcpmon_rsp_debug_config {
uint8_t global_level; /* Global debug level (0=NONE..5=TRACE) */
uint8_t category_count; /* Number of categories */
/* Followed by: char names_csv[]; null-terminated CSV: "UASYNC,LL_QUEUE,...\0" */
/* Followed by: uint8_t levels[category_count]; per-category levels */
};
/* CMD_SET_DEBUG_CONFIG: Client -> Server set debug levels */
struct etcpmon_cmd_set_debug {
uint8_t global_level; /* Global debug level (0=NONE..5=TRACE) */
uint8_t category_count; /* Number of categories */
uint8_t levels[ETCPMON_MAX_DEBUG_CATEGORIES]; /* Actual length = category_count */
};
/* RSP_ERROR: Error response */
struct etcpmon_rsp_error {
uint8_t error_code; /* Error code */
+5 -5
View File
@@ -76,10 +76,10 @@ allow=all
#tun_ip=100.64.1.1/24 # IP клиентского NAT TUN
#nat_via=0xABCD000000000001 # node_id провайдера (у кого запрашивать NAT)
# --- TCP Proxy (локальный TCP/UDP/ICMP прокси) ---
# Проксирует входящие TCP (через uIP), UDP и ICMP ping через указанный via_node.
# --- TCP Proxy Client (локальный TCP/UDP/ICMP прокси) ---
# Проксирует входящие TCP (через lwIP), UDP и ICMP ping через указанный via_node.
# Все три протокола идут через один и тот же удалённый узел.
#[tcp_proxy]
#[tcp_proxy_client]
#enabled=yes
#tun_name=tun_tcp # имя TUN интерфейса (по умолчанию tun_tcp)
#tun_ip=10.99.0.1 # IP адрес TUN интерфейса
@@ -89,7 +89,7 @@ allow=all
#forward=8000 -> 10.0.0.50:80
#forward=2222 -> 10.0.0.50:22
# --- Remote Proxy (удаленный exit node) ---
# --- TCP Proxy Server (удаленный exit node) ---
# Принимает CONNECT-запросы от других нод и открывает OS сокеты к адресатам.
#[remote_proxy]
#[tcp_proxy_server]
#enabled=yes