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

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

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

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

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

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

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

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

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

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

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

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

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

No files matched your search

+38 -2
View File
@@ -1,3 +1,6 @@
# Build directory (out-of-tree build)
/build/
# Build outputs
*.o
*.lo
@@ -6,10 +9,19 @@
*.so
*.so.*
utun
test_serialize
linux-x64-deb
# Temp files
*.c1
*.h2
1
# HTML files
index.html
# binary files
*.exe
**/*[^.]
# Autotools
Makefile
@@ -56,11 +68,20 @@ tests/*.log
tests/*.trs
tests/logs/
# Test binaries (files without extension in tests/)
# Mass update logs
logs/
# Test binaries (executables without extension), but track source/config files
tests/test_*
!tests/test_*.c
!tests/test_*.conf
!tests/test_*.json
tests/bench_*
!tests/bench_*.c
# All log files
*.log
utun.log*
# Local configs
*.cfg
@@ -68,8 +89,23 @@ tests/logs/
utun.conf
!utun.conf.sample
# IDE/Editor directories
.opencode/
# Tool binaries
tools/bping/bping
tools/proxy/udp_proxy
# Debug/temp artifacts
*.bak
*.debug
*.csv
__pycache__/
# MSVC temp files
cl
build_win.log
/wintun.dll
src/lwip_orig/
lib/timeout/
-102
View File
@@ -1,102 +0,0 @@
# Context7 Skill
## Purpose
Fetches **live, version-specific documentation** for external libraries and frameworks using the Context7 API. Ensures you always get current API patterns instead of potentially outdated training data.
**Golden Rule**: Always fetch live docs for external libraries—training data may be outdated.
## Quick Start
### Recommended: Use ExternalScout Subagent
The **ExternalScout** subagent is the recommended way to fetch external documentation. It handles:
- Library detection
- Query optimization
- Documentation filtering and sorting
- Formatted results with code examples
**Invocation**:
```
Use ExternalScout to fetch documentation for [Library Name]: [your specific question]
```
**Example**:
```
Use ExternalScout to fetch documentation for Drizzle ORM: How do I set up modular schemas with PostgreSQL?
```
### Alternative: Direct Skill Usage
You can also invoke the Context7 skill directly via bash:
```bash
# Step 1: Search for library
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY&query=TOPIC" | jq '.results[0]'
# Step 2: Fetch documentation
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=OPTIMIZED_QUERY&type=txt"
```
See `SKILL.md` for detailed API documentation.
## Supported Libraries
See `library-registry.md` for the complete list of supported libraries including:
- **Database & ORM**: Drizzle, Prisma
- **Authentication**: Better Auth, NextAuth.js, Clerk
- **Frontend**: Next.js, React, TanStack Query/Router/Start
- **Infrastructure**: Cloudflare Workers, AWS Lambda, Vercel
- **UI**: Shadcn/ui, Radix UI, Tailwind CSS
- **State**: Zustand, Jotai
- **Validation**: Zod, React Hook Form
- **Testing**: Vitest, Playwright
## Workflow
```
User Query
↓
ContextScout (searches internal context)
↓
No internal context found
↓
ContextScout recommends ExternalScout
↓
ExternalScout invoked
├─ Reads library-registry.md
├─ Detects library
├─ Loads query patterns
├─ Fetches from Context7 API
├─ Filters & sorts results
└─ Returns formatted documentation
↓
User receives current, actionable docs
```
## Files
- **`SKILL.md`** - Context7 API documentation and usage
- **`library-registry.md`** - Supported libraries, aliases, and query patterns
- **`README.md`** - This file (overview and quick start)
## Adding New Libraries
To add a new library to the registry:
1. Edit `library-registry.md`
2. Add entry under appropriate category:
```markdown
#### Library Name
- **Aliases**: `alias1`, `alias2`, `package-name`
- **Docs**: https://example.com/docs
- **Context7**: `use context7 for library-name`
- **Common topics**: topic1, topic2, topic3
```
3. (Optional) Add query optimization patterns
4. ExternalScout will automatically detect the new library
## Related
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
-85
View File
@@ -1,85 +0,0 @@
---
name: context7
description: Retrieve up-to-date documentation for software libraries, frameworks, and components via the Context7 API. This skill should be used when looking up documentation for any programming library or framework, finding code examples for specific APIs or features, verifying correct usage of library functions, or obtaining current information about library APIs that may have changed since training.
---
# Context7
## Overview
This skill enables retrieval of current documentation for software libraries and components by querying the Context7 API via curl. Use it instead of relying on potentially outdated training data.
## Workflow
### Step 1: Search for the Library
To find the Context7 library ID, query the search endpoint:
```bash
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY_NAME&query=TOPIC" | jq '.results[0]'
```
**Parameters:**
- `libraryName` (required): The library name to search for (e.g., "react", "nextjs", "fastapi", "axios")
- `query` (required): A description of the topic for relevance ranking
**Response fields:**
- `id`: Library identifier for the context endpoint (e.g., `/websites/react_dev_reference`)
- `title`: Human-readable library name
- `description`: Brief description of the library
- `totalSnippets`: Number of documentation snippets available
### Step 2: Fetch Documentation
To retrieve documentation, use the library ID from step 1:
```bash
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=TOPIC&type=txt"
```
**Parameters:**
- `libraryId` (required): The library ID from search results
- `query` (required): The specific topic to retrieve documentation for
- `type` (optional): Response format - `json` (default) or `txt` (plain text, more readable)
## Examples
### React hooks documentation
```bash
# Find React library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=react&query=hooks" | jq '.results[0].id'
# Returns: "/websites/react_dev_reference"
# Fetch useState documentation
curl -s "https://context7.com/api/v2/context?libraryId=/websites/react_dev_reference&query=useState&type=txt"
```
### Next.js routing documentation
```bash
# Find Next.js library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=nextjs&query=routing" | jq '.results[0].id'
# Fetch app router documentation
curl -s "https://context7.com/api/v2/context?libraryId=/vercel/next.js&query=app+router&type=txt"
```
### FastAPI dependency injection
```bash
# Find FastAPI library ID
curl -s "https://context7.com/api/v2/libs/search?libraryName=fastapi&query=dependencies" | jq '.results[0].id'
# Fetch dependency injection documentation
curl -s "https://context7.com/api/v2/context?libraryId=/fastapi/fastapi&query=dependency+injection&type=txt"
```
## Tips
- Use `type=txt` for more readable output
- Use `jq` to filter and format JSON responses
- Be specific with the `query` parameter to improve relevance ranking
- If the first search result is not correct, check additional results in the array
- URL-encode query parameters containing spaces (use `+` or `%20`)
- No API key is required for basic usage (rate-limited)
@@ -1,290 +0,0 @@
# External Library Registry
## Purpose
This file lists external libraries/frameworks that should use **ExternalScout** (via Context7) for live documentation instead of relying on potentially outdated training data.
## When to Use This
**ContextScout** checks this list when:
1. User asks about a library/framework
2. No internal context exists in `.opencode/context/development/frameworks/`
3. Query matches a library name below
**Action**: Recommend **ExternalScout** subagent
---
## Supported Libraries
### Database & ORM
#### Drizzle ORM
- **Aliases**: `drizzle`, `drizzle-orm`, `drizzle orm`
- **Docs**: https://orm.drizzle.team/
- **Context7**: `use context7 for drizzle`
- **Common topics**: schema organization, migrations, relational queries, transactions, TypeScript types
#### Prisma
- **Aliases**: `prisma`
- **Docs**: https://www.prisma.io/docs
- **Context7**: `use context7 for prisma`
- **Common topics**: schema, migrations, client, relations, TypeScript
---
### Authentication
#### Better Auth
- **Aliases**: `better-auth`, `better auth`, `betterauth`
- **Docs**: https://www.better-auth.com/docs
- **Context7**: `use context7 for better-auth`
- **Common topics**: Next.js integration, Drizzle adapter, social providers, session management, 2FA
#### NextAuth.js
- **Aliases**: `nextauth`, `next-auth`, `nextauth.js`
- **Docs**: https://next-auth.js.org/
- **Context7**: `use context7 for nextauth`
- **Common topics**: providers, callbacks, sessions, JWT
#### Clerk
- **Aliases**: `clerk`
- **Docs**: https://clerk.com/docs
- **Context7**: `use context7 for clerk`
- **Common topics**: authentication, user management, organizations
---
### Frontend Frameworks
#### Next.js
- **Aliases**: `nextjs`, `next.js`, `next`
- **Docs**: https://nextjs.org/docs
- **Context7**: `use context7 for nextjs`
- **Common topics**: App Router, Server Actions, Server Components, routing, middleware, API routes
#### React
- **Aliases**: `react`, `reactjs`, `react.js`
- **Docs**: https://react.dev/
- **Context7**: `use context7 for react`
- **Common topics**: hooks, components, state, effects, context
#### TanStack Query
- **Aliases**: `tanstack query`, `react query`, `@tanstack/react-query`
- **Docs**: https://tanstack.com/query/latest
- **Context7**: `use context7 for tanstack query`
- **Common topics**: useQuery, useMutation, prefetching, caching, Server Components
#### TanStack Router
- **Aliases**: `tanstack router`, `@tanstack/react-router`
- **Docs**: https://tanstack.com/router/latest
- **Context7**: `use context7 for tanstack router`
- **Common topics**: routing, type-safe routes, loaders, navigation
#### TanStack Start
- **Aliases**: `tanstack start`, `@tanstack/start`
- **Docs**: https://tanstack.com/start/latest
- **Context7**: `use context7 for tanstack start`
- **Common topics**: full-stack setup, server functions, file routing
---
### Infrastructure & Deployment
#### Cloudflare Workers
- **Aliases**: `cloudflare workers`, `cloudflare`, `workers`, `cf workers`
- **Docs**: https://developers.cloudflare.com/workers
- **Context7**: `use context7 for cloudflare workers`
- **Common topics**: routing, KV storage, Durable Objects, bindings, middleware
#### AWS Lambda
- **Aliases**: `aws lambda`, `lambda`, `aws λ`
- **Docs**: https://docs.aws.amazon.com/lambda
- **Context7**: `use context7 for aws lambda`
- **Common topics**: handlers, layers, environment variables, triggers, TypeScript
#### Vercel
- **Aliases**: `vercel`
- **Docs**: https://vercel.com/docs
- **Context7**: `use context7 for vercel`
- **Common topics**: deployment, environment variables, edge functions, serverless
---
### UI Libraries & Styling
#### Shadcn/ui
- **Aliases**: `shadcn`, `shadcn/ui`, `shadcn-ui`
- **Docs**: https://ui.shadcn.com/
- **Context7**: `use context7 for shadcn`
- **Common topics**: components, installation, theming, customization
#### Radix UI
- **Aliases**: `radix`, `radix ui`, `radix-ui`, `@radix-ui`
- **Docs**: https://www.radix-ui.com/
- **Context7**: `use context7 for radix`
- **Common topics**: primitives, accessibility, composition
#### Tailwind CSS
- **Aliases**: `tailwind`, `tailwindcss`, `tailwind css`
- **Docs**: https://tailwindcss.com/docs
- **Context7**: `use context7 for tailwind`
- **Common topics**: configuration, utilities, responsive design, dark mode
---
### State Management
#### Zustand
- **Aliases**: `zustand`
- **Docs**: https://zustand-demo.pmnd.rs/
- **Context7**: `use context7 for zustand`
- **Common topics**: store creation, selectors, middleware, TypeScript
#### Jotai
- **Aliases**: `jotai`
- **Docs**: https://jotai.org/
- **Context7**: `use context7 for jotai`
- **Common topics**: atoms, async atoms, utilities
---
### Validation & Forms
#### Zod
- **Aliases**: `zod`
- **Docs**: https://zod.dev/
- **Context7**: `use context7 for zod`
- **Common topics**: schema validation, TypeScript inference, parsing, refinements
#### React Hook Form
- **Aliases**: `react hook form`, `react-hook-form`, `rhf`
- **Docs**: https://react-hook-form.com/
- **Context7**: `use context7 for react hook form`
- **Common topics**: register, validation, errors, TypeScript
---
### Testing
#### Vitest
- **Aliases**: `vitest`
- **Docs**: https://vitest.dev/
- **Context7**: `use context7 for vitest`
- **Common topics**: configuration, testing, mocking, coverage
#### Playwright
- **Aliases**: `playwright`
- **Docs**: https://playwright.dev/
- **Context7**: `use context7 for playwright`
- **Common topics**: browser automation, testing, selectors, assertions
---
## Detection Patterns
ContextScout and ExternalScout should match queries containing:
- Library name (case-insensitive)
- Common variations (e.g., "next.js" vs "nextjs")
- Package names (e.g., "@tanstack/react-query")
**Examples**:
- "How do I use **Drizzle** with PostgreSQL?" → Match: Drizzle ORM
- "Show me **Next.js** App Router setup" → Match: Next.js
- "**TanStack Query** with Server Components" → Match: TanStack Query
- "**Better Auth** integration" → Match: Better Auth
---
## Query Optimization Patterns
### Drizzle ORM
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup/Installation | `PostgreSQL+setup+configuration+TypeScript+installation` |
| Modular schemas | `modular+schema+organization+domain+driven+design` |
| Relations | `relational+queries+one+to+many+joins+with+relations` |
| Migrations | `drizzle-kit+migrations+generate+push+PostgreSQL` |
| Transactions | `database+transactions+patterns+TypeScript` |
| Type safety | `TypeScript+type+inference+schema+types+inferInsert` |
### Better Auth
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `setup+configuration+Next.js+TypeScript+installation` |
| Next.js integration | `Next.js+App+Router+integration+setup+configuration` |
| Drizzle adapter | `Drizzle+adapter+PostgreSQL+schema+generation+configuration` |
| Social providers | `social+providers+OAuth+GitHub+Google+setup` |
| Email/password | `email+password+authentication+signup+signin` |
| Session management | `session+management+cookies+JWT+middleware` |
### Next.js
| User Intent | Optimized Query |
|-------------|-----------------|
| App Router | `App+Router+file+conventions+layouts+pages+routing` |
| Server Actions | `Server+Actions+form+mutations+revalidation+TypeScript` |
| Server Components | `React+Server+Components+async+data+fetching+patterns` |
| Dynamic routes | `dynamic+routes+params+TypeScript+generateStaticParams` |
| Middleware | `middleware+authentication+redirects+headers+cookies` |
| API routes | `API+routes+route+handlers+TypeScript+POST+GET` |
### TanStack Query
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `setup+QueryClient+provider+Next.js+TypeScript` |
| Data fetching | `useQuery+data+fetching+TypeScript+patterns+async` |
| Mutations | `useMutation+optimistic+updates+invalidation+TypeScript` |
| Prefetching | `prefetchQuery+Server+Components+hydration+Next.js` |
| Caching | `cache+configuration+staleTime+gcTime+invalidation` |
### Cloudflare Workers
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `getting+started+setup+TypeScript+wrangler+configuration` |
| Routing | `routing+itty-router+hono+request+handling` |
| KV storage | `KV+storage+key+value+bindings+TypeScript` |
| Durable Objects | `Durable+Objects+state+WebSockets+coordination` |
### AWS Lambda
| User Intent | Optimized Query |
|-------------|-----------------|
| Setup | `getting+started+setup+TypeScript+handler+configuration` |
| Handlers | `handler+function+event+context+TypeScript+patterns` |
| Layers | `layers+dependencies+shared+code+deployment` |
| Environment variables | `environment+variables+secrets+configuration+SSM` |
---
## Adding New Libraries
To add a new library:
1. Add entry under appropriate category
2. Include: Name, aliases, docs link, Context7 command, common topics
3. (Optional) Add query optimization patterns
4. Update ExternalScout if needed (usually automatic)
**Template**:
```markdown
#### Library Name
- **Aliases**: `alias1`, `alias2`, `package-name`
- **Docs**: https://example.com/docs
- **Context7**: `use context7 for library-name`
- **Common topics**: topic1, topic2, topic3
```
---
## Usage by ExternalScout
ExternalScout uses this file to:
1. **Detect** which library the user is asking about
2. **Load** query optimization patterns for that library
3. **Build** optimized Context7 queries
4. **Fetch** live documentation
5. **Return** filtered, relevant results
-51
View File
@@ -1,51 +0,0 @@
# Context7 Skill Navigation
**Purpose**: Live documentation fetching for external libraries via Context7 API
---
## Structure
```
context7/
├── navigation.md # This file
├── README.md # Quick start and workflow
├── SKILL.md # Context7 API documentation
└── library-registry.md # Supported libraries and query patterns
```
---
## Quick Routes
| Task | Path |
|------|------|
| **Quick start** | `README.md` |
| **API reference** | `SKILL.md` |
| **Supported libraries** | `library-registry.md` (lines 18-181) |
| **Query patterns** | `library-registry.md` (lines 199-261) |
| **Add new library** | `library-registry.md` (lines 264-279) |
| **ExternalScout integration** | `README.md` (lines 9-26) |
---
## By Purpose
**Using Context7**:
- Quick start → `README.md`
- API details → `SKILL.md`
**Adding Libraries**:
- Template → `library-registry.md` (lines 272-279)
- Supported list → `library-registry.md` (lines 18-181)
**Integration**:
- ContextScout workflow → `README.md` (lines 54-73)
- ExternalScout subagent → `.opencode/agent/subagents/core/externalscout.md`
---
## Related
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
+224 -160
View File
@@ -3,58 +3,80 @@
Ты - профессиональный программист высокого уровня.
Ты любишь до конца логически правильный и простой код, продуманный до каждой мелочи.
Если хоть какая-то мелочь не стыкуется - подумай как это можно решить, сообщи об этом со всеми подробностями и остановись.
Если что-то получается нелогично или громоздко - хорошо подкмай как сделать просто и компактно. предложи варианты и остановись.
Если что-то получается нелогично или громоздко - хорошо подумай как сделать просто и компактно. предложи варианты и остановись.
Имей, загружай когда надо полный код нужных тебе функций/структур. Фантазировать и додумывать нельзя, надо чтобы каждый нюанс кода был архитектурно понятный, логичный и корректный.
Надо детально разобратсья в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
Старайся одно логичеси завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
Надо детально разобраться в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
Старайся одно логически завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
This file contains essential information for AI coding agents working in the uTun codebase.
## 📋 Quick Reference
## Quick Reference
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** TinyCrypt (AES-CCM, ECC)
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
## 🔧 Build Commands
## Build Commands
### Full Build
win: powershell build_full.bat (в точности как написано без дополнительных опций powershell, текущий каталог не важен)
### Full Build (Linux)
```bash
./configure # Configure build
make # Build everything
make install # Install (requires sudo)
./build.sh --full -j4 # autoreconf + configure + make
```
### Partial Builds
win: powershell -Command ".\build.bat" 2>&1 (аналог make если не изменен makefile.am)
логи сборки win: build_win.log
### Full Build (Windows/MSYS2)
```powershell
powershell build_full.bat # запускает bash build.sh --full через MSYS2 UCRT64
```
### Incremental Build
```bash
cd lib && make # Build only the library
cd src && make # Build only the main program
cd tests && make # Build only tests
./build.sh -j4 # make с авто-конфигурацией если надо
```
```powershell
powershell -Command ".\build.bat" 2>&1 # Windows, логи: build_win.log
```
### Clean Build
```bash
make clean # Clean object files
make distclean # Clean everything including configure files
make clean # Clean object files
make distclean # Clean everything including configure files
./build.sh --clean -j4 # Clean then rebuild
```
## 🧪 Test Commands
### Partial Builds
```bash
cd lib && make # Build only the library (libuasync.a)
cd src && make # Build only the main program
cd tests && make # Build only tests
```
### Direct Build (Windows, без autotools)
```bash
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
```
## Test Commands
### Run All Tests
win: powershell check.bat
```bash
make check # Run all tests via automake
make check # Run all tests via automake, логи в tests/logs/
```
```powershell
powershell check.bat # Windows, запускает каждый .exe из tests/
```
### Run Specific Test
```bash
cd tests/ && ./test_etcp_crypto # ETCP crypto test
cd tests/ && ./test_etcp_simple # Simple ETCP test
cd tests/ && ./test_ecc_encrypt # ECC encryption test
cd tests/
./test_etcp_crypto
./test_etcp_two_instances
./test_etcp_simple_traffic
./test_pkt_normalizer_etcp
./test_etcp_api
./test_ll_queue
./test_nat_detection
./test_bgp_route_exchange
```
### Run Single Test with Debug Info
@@ -65,7 +87,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
./my_test
```
## 💻 Code Style Guidelines
## Code Style Guidelines
### Naming Conventions
- **Functions:** `snake_case` - `etcp_connection_create()`, `sc_encrypt()`
@@ -85,7 +107,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
}
```
- **Comments:** Primary language is Russian for business logic, English for API docs
- **Line Length:** Aim for 80-100 characters
- **Line Length:** Aim for 80-100 characters, but can go up to 150 if logically coherent
### Include Order
```c
@@ -108,195 +130,237 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize: %s", err);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket created on port %d", port);
```
- Во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
## 🔐 Cryptography Guidelines
### Memory Management
- Use `u_malloc`/`u_calloc`/`u_realloc`/`u_free`/`u_strdup` from `lib/mem.h` (wrappers with leak tracking)
- Memory pools: `memory_pool_alloc()` / `memory_pool_free()` for hot-path allocations
- Queue entries: `queue_entry_new_from_pool()` / `queue_entry_free()` / `queue_dgram_free()`
## Cryptography Guidelines
### Key Sizes
| Constant | Value | Description |
|----------|-------|-------------|
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
| `SC_TAG_SIZE` | 16 | CCM auth tag |
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
### Using Secure Channel (secure_channel.h)
```c
// 1. Initialize context
struct SC_MYKEYS my_keys;
// ... fill keys ...
sc_init_local_keys(&my_keys, public_key_hex, private_key_hex);
// or sc_generate_keypair(&my_keys);
sc_context_t ctx;
sc_init_ctx(&ctx, &my_keys);
// 2. Set peer public key (for key exchange)
sc_set_peer_public_key(&ctx, peer_public_key, 0); // 0 = binary format
sc_set_peer_public_key(&ctx, peer_public_key, SC_PEER_PUBKEY_HEX); // 0=bin, 1=hex
// 3. Ready for encrypt/decrypt
sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ciphertext_len);
sc_decrypt(&ctx, ciphertext, ciphertext_len, plaintext, &plaintext_len);
// 4. Pubkey obfuscation (used in INIT/PING packets):
// salt(8) + XOR(SHA256(salt||peer_pubkey) || SHA256(peer_pubkey||salt), my_pubkey)
sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
```
### Important Notes
- **Nonce size:** Must be exactly 13 bytes for CCM mode (`SC_NONCE_SIZE = 13`)
- **Session key:** 16 bytes for AES-128 (`SC_SESSION_KEY_SIZE = 16`)
- **Tag size:** 8 bytes for authentication (`SC_TAG_SIZE = 8`)
- **Error codes:** Check return values, negative = error
- **Encryption:** 3-byte header (timestamp uint16_t + flag_up uint8_t) + data_len bytes encrypted
- **INIT packets:** Header + data encrypted, pubkey+salt block (SC_PUBKEY_ENC_SIZE bytes) appended unencrypted
- **Nonce:** Must be exactly 13 bytes for CCM mode
- **Error codes:** Check return values, negative = error (SC_OK=0, SC_ERR_* < 0)
## 🏗️ Architecture
## Debug System
### Debug Levels (по возрастанию)
`none < error < warn < info < debug < trace`
### Debug Categories (21 категория)
```
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
```
### Настройка отладки
- В конфиге: `debug = etcp=trace,config=info` (формат: `категория=уровень,...`)
- В коде: глобальный уровень и per-category уровни из `debug_config_t g_debug_config`
- Макросы: `DEBUG_ERROR(cat,fmt,...)` `DEBUG_WARN` `DEBUG_INFO` `DEBUG_DEBUG` `DEBUG_TRACE`
- `log_dump(prefix, data, len)` — hex dump в лог
### Dual Output
- Консоль и файл настраиваются раздельно (`debug_set_console_level`, `debug_set_file_level`)
- `debug_enable_file_output(path, truncate)` / `debug_disable_file_output()`
## Architecture
### Directory Structure
```
├── lib/ # Core libraries
├── src/ # Main source code
├── tests/ # Test programs
├── lib/ # Core libraries (13 .c + 14 .h)
├── src/ # Main source code (28 .c + 23 .h)
├── tests/ # 31+ test programs
├── doc/ # Technical Specifications
├── tools/ # Auxiliary tools
│ ├── etcpmon/ # GUI монитор ETCP
│ ├── proxy/ # UDP прокси для тестов
│ └── bping/ # BPing (bandwidth ping)
├── tinycrypt/ # TinyCrypt crypto library (external)
└── net_emulator/ # Network emulator
├── net_emulator/ # Network emulator (delays, loss, reordering)
└── c2/ # Test instance 2 (конфиг и бинарник для тестов)
```
### File Overview
**Core (src/)**
- `utun.c` - Main program entry point, CLI parsing, daemon mode
- `utun_instance.c/h` - Root instance lifecycle management
- `tun_if.c/h` - Simplified TUN interface (init/write/close)
- `utun_instance.c/h` - Root instance lifecycle, config loading, all submodule init
- `tun_if.c/h` - TUN interface API (init/write/close, cross-platform)
- `tun_linux.c` `tun_freebsd.c` `tun_windows.c` - Platform-specific TUN implementations
- `tun_route.c/h` - TUN routing table sync
**Network Stack (src/)**
- `etcp.c/h` - ETCP protocol implementation (TCP-like with crypto)
- `etcp_connections.c/h` - Socket and link management
- `etcp_loadbalancer.c/h` - Multi-link load balancing
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly
- `routing.c/h` - Routing table management
- `etcp.c/h` - ETCP protocol implementation (inflight queues, retrans, ACK, RTT/jitter)
- `etcp_api.c/h` - ETCP public API (send/recv/bind callbacks)
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
- `etcp_debug.c/h` - ETCP packet dump/formatting
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
- `packet_dump.c/h` - Packet hex dump utility
- `firewall.c/h` - Firewall rules (per-interface filtering)
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
**Routing (src/)**
- `routing.c/h` - Routing table management (local routes)
- `route_lib.c/h` - Routing library utilities
- `route_bgp.c/h` - BGP-style route exchange between peers
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
- `route_node.c/h` - Route node (peer) management
**Crypto (src/)**
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
- `crc32.c/h` - CRC32 checksums
**NAT (src/)**
- `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine
- `nat_transport.c/h` - NAT transport layer (packet relay)
**Config (src/)**
- `config_parser.c/h` - INI-style config file parsing
- `config_updater.c/h` - Config file modification utilities
**Control (src/)**
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
**Libraries (lib/)**
- `u_async.c/h` - Async event loop (epoll/poll, timers)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks
- `memory_pool.c/h` - Fast object pool allocator
- `debug_config.c/h` - Debug logging system
- `timeout_heap.c/h` - Min-heap for timer management
- `sha256.c/h` - SHA256 hashing
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
- `memory_pool.c/h` - Fast object pool allocator (pre-allocated blocks)
- `debug_config.c/h` - Debug logging system (levels, categories, dual output, runtime config)
- `timeout_heap.c/h` - Min-heap for timer management (used by u_async)
- `sha256.c/h` - SHA256 hashing (fallback; OpenSSL preferred via USE_OPENSSL)
- `mem.c/h` - Memory wrappers with leak tracking (`u_malloc`/`u_free`/`u_calloc`/`u_strdup`)
- `serialize.c/h` - Binary serialization utilities
- `swm_min.c/h` - Sliding window minimum (for RTT min tracking)
- `getmyip.c/h` - Get local IP / default route detection
- `myip.c` - My IP helper (no header)
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
**Tests (tests/)**
- `test_etcp_*.c` - ETCP protocol tests
- `test_crypto.c` - TinyCrypt AES/CCM tests
- `test_ecc_encrypt.c` - ECC encryption tests
- `test_ll_queue.c` - Queue functionality tests
- `bench_*.c` - Performance benchmarks
**External**
- `tinycrypt/` - TinyCrypt library (AES, ECC, SHA256)
**Memory Pools in UTUN_INSTANCE:**
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
- `pkt_pool` — для struct ETCP_DGRAM (сетевые пакеты на отправку/приём)
- `ack_pool` — для struct ACK_PACKET (подтверждения приёма)
### Key Components
- **UASYNC:** Asynchronous event loop for sockets and timers
- **LL_QUEUE:** Lock-free lockstep queue for packet handling
- **Memory Pool:** Fast allocation for network packets
- **ETCP:** Extended TCP protocol with crypto and reliability
- **Secure Channel:** AES-CCM encryption with ECC key exchange
- **UASYNC:** One per thread. Async event loop (epoll/poll), timers via timeout_heap
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
## 🐛 Debugging Tips
## Queue Usage Rules
### Enable Debug Output
```c
// In source files, define debug before including headers
#define DEBUG_CATEGORY_YOUR_MODULE 1
```
## Работа с очередями. Важно!
С очередью следуею работать придерживаясь следующих правил:
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
### Common Build Issues
1. **Missing headers:** Check include paths in Makefile.am
2. **Undefined references:** Ensure source files are listed in Makefile.am
3. **Link errors:** Check function declarations match definitions
### Запись в очередь
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
## 📦 Dependencies
### Чтение из очереди
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
- Внутри коллбэка ОБЯЗАТЕЛЬНО: `queue_data_get(q)` → обработать элемент → `queue_resume_callback(q)`
- Без вызова `queue_resume_callback` очередь навсегда застрянет
### Build Dependencies
- autoconf, automake, libtool
- gcc with C99 support
- pthread library
- OpenSSL/crypto library (for SHA256)
### Поиск
- `queue_data_put_with_index(q, entry)` — добавляет с индексом для быстрого поиска (offset/size заданы при queue_new)
- `queue_find_data_by_index(q, key)` — поиск по индексу через хеш-таблицу (размер ключа из q->index_size)
### Runtime Dependencies
- TUN/TAP interface support (Linux kernel module)
- libcrypto (usually installed by default)
- Proper network configuration for testing
## u_async Rules
- Нельзя использовать в одном потоке несколько u_async. Один поток = всегда 1 uasync instance
- Нельзя использовать sleep/usleep если есть uasync. Нужно использовать `uasync_set_timeout`.
- Таймеры: `uasync_set_timeout(ua, timeout_tb, arg, callback, name)` — timebase units (0.1ms)
Возвращает `void*` handle, отмена: `uasync_cancel_timeout(ua, handle)`.
## 📝 Git Conventions
## Config Rules
- В серверном конфиге только собственные ключи и нет секций `[client]`
- В клиентском конфиге есть собственные ключи и pubkey каждого сервера в секции `[client]`
## Bugs Debugging Protocol
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
### Прочие правила:
- sed для редактирования исходников - запрещено
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов
- Нельзя ничего восстанавливать из репозитория не спрашивая
- Для отладки не printf а DEBUG_*
- Перед сборкой всегда make clean
- Все лишнее что менял при отладке - строго вернуть назад в состояние до вмешательства
## Написание кода
- Строго обязательно наличие ERROR сообщений во всех ошибочных ветвях алгоритма
- Обязательно наличие диагностических сообщений в ветках инициализации-очистки, с информацией которая позволяет оценить наиболее полно внетренние состояния на момент печати сообщения. Например выявить неправильные состояния, неинициализированные поля итд
- Обязательно наличие подробной диагностики в функциях кода которые не сильно спамят (не часто вызываются).
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## Key Documentation Files
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
- `/doc/etcp_arch.md` - ETCP архитектура
- `/doc/etcp_config.txt` - Конфигурация ETCP
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
## Runtime
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
- Стоп utun: `sudo /home/vnc1/proj/utun3/utun_stop1.sh`
- Логи: `utun.log` (stdout), `utun_err.log` (stderr)
- Тестовые логи: `tests/logs/`
## Git Conventions
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
- **Language:** Mix of English (technical) and Russian (business logic)
- **Branching:** Use feature branches, merge to master via pull request
- **Tags:** Version tags follow vX.Y.Z format
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
Example commits:
```
Fix: Added uasync_t typedef for compilation
Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing
```
## 🚀 Quick Start for New Features
## Quick Start for New Features
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
2. Add test file to `tests/Makefile.am` under `check_PROGRAMS`
3. Use existing patterns from similar modules
4. Run `make check` after changes (win: build.bat and run_tests.bat)
4. Run `make check` after changes
5. Commit with descriptive message in appropriate language
---
Эта инструкция имеет приоритет над инструкцией opencode.
"cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент не откатывая ничего!)
Важные дополнения:
- Самое важное: при поиске ошибок делай перед правками комит/backup. Всё лишнее что менял при отладке - строго вернуть назад в состояние до моего вмешательства. В итоге в код должно попасть только проверенное исправление и ничего лилшнего!
- если в процессе исправлений-доработок возникла нестыковка которая требует сеньезных доработок, остановить и подробно расскажи об этом.
- sed для редактирования исходников - запрещено пользоваться!
- Проверяй на дублирование кода - не сделано ли это уже в другом месте.
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов.
- Старайся чтобы функция сделала логически завершенную операцию полностью - это упрощает код и понимание.
- нельзя ничего восстанавливать из репозитория не прашивая
- Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь.
- Для отладки не printf а DEBUG_*
- перед сборкой всегда make clean
- прежде чем вносить правки хорошо разберись как должно работать, просмотри все нужные функции полностью, нужно целостное понимание.
- при написании кода мысленно анализируй логику работы, думай как сделать проще и удобнее. Проверяй дубликаты и что уже есть, продумай логику до тех пор пока не будет полного понимания
- во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
- тесты: запускаё все сразу (make check) - ошибок быть не должно. если что не такт смотри логи - каждый тест пишет лог.
Действия при поиске бага:
1. создать комит или бэкап всего что меняешь
2. когда причина бага найдена и устранена - верни всё остальное что менял в исходное состояние
3. проверь что тесты проходят и всё работает. make clean перед сбркой обязательно.
4. если баг не устранён - продолжай поиск или если время заканчивается - верни всё в исходное состояние
Как более точно эффективно проводить диагностику ошибки:
0. Сосредоточься на поиске конкретной ошибки и доведи его до конца руководствуясь этой инструкцией.
1. прочитай полностью код функций с ошибкой и код всех функции которые учавствуют в ошибочном алгоритме.
2. еще раз мысленно выполни предполагаемый сценарий ошибки (если чего-то не хватает до полной картины - обязательно дочитывай все ветви функций по ходу: нельзя додумывать - нужна точность) и убедись что:
- ты точно понимаешь как алгоритм прихдит к ошибке
- все функции которые учавствуют в сценарии ошибки проанализированы и их поведение проверено и понятно
- последовательно отсекай логически законченные блоки которые проверены и точно правильно работают
- если ты полностью проанализировал функцию и к ней нет описания или описание неточное (неполное), и функция работает логически корректно - поправь описание. Если функция работает логически неверно - добавь запись об этом в todo.txt
3. Если исправление как либо меняет поведение функции:
- просмотри где используется эта функция
- убедись что изменение поведения не повлияет на остальные места где функция используется. особенно важный пункт для библиотек и модулей коммуникаций
Работа с очередями.
- Запись в очередь: очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой. Пользуйся наблюдателем queue_wait_threshold, он специально сделан для добавления в очередь.
- Чтение из очереди: пользуйся queue_set_callback: при вызове callback , обработай один или несколько элементов, потом вызови resume_callback.
Работа с u_async
- нельзя использовать в одном потоке несколько u_async. один поток = всегда 1 uasync instance
- нельзя использовать sleep/usleep если есть uasync. Нужно использовать uasync_set_timeout.
Конфиги:
- в серверном только собственные ключи и нет секций [client]
- в клиентском есть собственные ключи и pubkey каждого сервера в секции [client]
тех задания для реализации в каталоге /doc.
/doc/etcp_protocol.txt - основной протокол (похож на TCP+QUIC, поддеиживает шифрования, load balancing multi-link, работу а неустойчивых каналах, утилизацию полосы и недопущение перегрузки каналов связи)
- реализация в /src/etcp*.c/h
запуск utun от root (для tun) - /home/vnc1/proj/utun3/utun_start.sh
стоп utun: sudo /home/vnc1/proj/utun3/utun_stop1.sh
логи - utun.log
*Last updated: 2026-02-13 - c_util fully functional with toc/description/show/edit commands*
+253
View File
@@ -0,0 +1,253 @@
========================================================================
uTun3 AUDIT FINDINGS — Сводный отчёт
========================================================================
Аудит от 14.05.2026 — обход основных модулей
(use-after-free, out-of-bounds, утечки, reentrancy)
========================================================================
HIGH (19 проблем)
========================================================================
1. u_async.c:137-165 use-after-free
Частичный успех u_realloc в socket_array_add_internal: если часть
реаллоков прошла, а часть нет — старые массивы уже освобождены,
новые не выделены, sa->sockets/sa->fd_to_index/sa->index_to_fd
указывают на освобождённую память.
2. u_async.c:842,1167 логическая ошибка
process_posted_tasks никогда не вызывается на Linux.
В epoll-пути: drain_wakeup_pipe не вызывает handle_wakeup/process_posted_tasks.
В poll-пути: wakeup-слот (i==0) обрабатывается с drain_wakeup_pipe и continue,
минуя зарегистрированный коллбэк.
Следствие: uasync_post из другого потока не доставляет задачи — posted_tasks
накапливаются бесконечно.
3. u_async.c:1383,1398,1418 утечка памяти
При сбое создания wakeup (pipe/socket) в uasync_create не освобождаются:
ua->sockets, ua->timeout_heap, ua->timeout_pool, ua->epoll_fd.
4. memory_pool.c:51-69 double-free
Нет детекта double-free. Повторный free создаёт цикл во freelist,
memory_pool_alloc выдаёт один и тот же блок двум потребителям.
5. etcp.c:679-683 утечка памяти
input_queue_cb: при исчерпании inflight_pool вызывается
queue_entry_free(in_pkt) без queue_dgram_free(&in_pkt->ll).
dgram из data_pool утекает.
6. etcp_connections.c:87,876-926 use-after-free
burst_resp_timer не отменяется в etcp_link_close.
Коллбэк burst_resp_timeout_cb срабатывает на освобождённом link.
7. etcp_connections.c:846-854 use-after-free
stats_timer не отменяется при провале insert_link в etcp_link_new.
link_stats_timer_cb срабатывает на освобождённом link.
8. etcp_connections.c:879-886 use-after-free
Ветка conn==NULL в etcp_link_close пропускает отмену ВСЕХ таймеров
(stats_timer, init_timer, shaper_timer, keepalive_timer, burst_resp_timer).
Любой из них сработает на освобождённом link.
9. etcp_connections.c:1388,1531,1561 out-of-bounds read
INIT request парсинг: проверка pkt_len < 12 должна быть pkt_len < 26
(23 байта заголовка). Пакет с длиной 12-25 пройдёт проверку,
но читает за границей расшифрованных данных.
10. pkt_normalizer.c:33 null deref
if (!pn) pn->alloc_errors++ — разыменование NULL после u_calloc.
11. pkt_normalizer.c:300 null deref
pn_buf_renew в цикле without проверки возврата → memcpy(NULL, ...)
при сбое memory_pool_alloc.
12. eim_nat.c:80-93 логическая ошибка
Переполнение uint16_t порта: next_port с 65535→0, затем порты
ниже port_start выделяются (включая порт 0). Соединения нерабочие.
13. eim_nat.c (весь файл) логическая ошибка
Нет механизма истечения/удаления NAT-записей. Таблица навсегда
исчерпывается при долгой работе, eim_nat_egress начинает отказывать.
14. route_bgp.c:749,762-766 утечка памяти
route_bgp_process_nodeinfo: при пересоздании nodeinfo1 старая
очередь paths сохраняется в локальную переменную, но затем
перезаписывается новой queue_new() — утечка.
15. route_bgp.c:904-907 утечка памяти
route_bgp_send_nodeinfo: при провале etcp_send не освобождаются
p (u_malloc) и e (queue_entry_new).
16. route_ping.c:126-134 утечка памяти
route_ping_cancel_for_conn: nat_check_arg (p->arg, u_calloc) не
освобождается. В нормальном и таймаутном путях коллбэк освобождает,
но cancel-путь коллбэк не вызывает.
17. ll_queue.c:321,439 reentrancy
Синхронный вызов коллбэка при первом queue_data_put (count==1)
без отложенного возобновления через uasync → рекурсивный
ack_timeout_check с устаревшим указателем current.
18. secure_channel.c:379,711 потенциальный OOB write
sc_encrypt пишет в caller buffer без параметра capacity.
Контракт API опасен: вызывающий должен сам гарантировать размер буфера.
19. secure_channel.c:432,756 потенциальный OOB write
sc_decrypt — аналогично, нет проверки capacity выходного буфера.
========================================================================
MEDIUM (18 проблем)
========================================================================
20. u_async.c:591-639 use-after-free
Stale handle в uasync_cancel_timeout/uasync_call_soon_cancel.
Нет инвалидации (generation counter), пул может переиспользовать память.
21. memory_pool.c:8,38,61,95 race condition
Глобальная g_total_free изменяется без блокировок, общая для всех пулов.
22. memory_pool.c:51-69 потенциальный OOB write
Нет проверки принадлежности блока пулу. Free в чужой пул вызывает
memset с чужим object_size — частичная очистка или запись за границу.
23. timeout_heap.c:43-45 утечка / несоответствие документации
Документация обещает free() при free_callback==NULL, код этого не делает.
24. etcp.c:718-763 reentrancy
Вложенный ack_timeout_check (через input_send_q_cb→etcp_request_pkt→
wait_ack_cb) обрабатывает не тот entry, т.к. outer loop использует
current, который мог быть перемещён/удалён внутренним вызовом.
25. etcp.c:747,944 утечка / дрейф счётчиков
Непроверенный queue_data_put_with_index: при провале счётчики
(retransmissions_count, inflight_bytes) не откатываются.
26. etcp_connections.c:1731,1763 out-of-bounds read
INIT_RESPONSE парсинг: нужно >=19 байт, проверяется только >=3.
27. etcp_connections.c:748-753 use-after-free + бесконечный цикл
etcp_socket_remove: при link->conn==NULL remove_link пропускается,
num_channels не уменьшается, цикл перечитывает освобождённый conn->links[0].
28. etcp_connections.c:1736-1738 логическая ошибка
link_status перезаписывается значением remote_keepalive, игнорируя
локальный recv_keepalive. Временно помечает link UP даже когда
локальный приём мёртв.
29. pkt_normalizer.c:43 integer overflow/underflow
frag_size = mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE.
При mtu < 158 беззнаковое вычитание даёт ~65379 → огромные буферы.
30. pkt_normalizer.c:240 утечка памяти
При провале queue_data_put фрагмент и dgram не освобождаются.
31. eim_nat.c:97,173 null deref
Нет проверки ctx->initialized/ctx->table в egress/ingress.
32. eim_nat.c:268,293 логическая ошибка
Статические пробросы (port forward) вне port range недоступны.
33. secure_channel.c:66 key hygiene
Нет sc_free_ctx — сессионный ключ не зануляется при уничтожении контекста.
34. secure_channel.c:322,620 key hygiene
ECDH shared secret остаётся на стеке незанулённым после
sc_derive_session_key.
35. secure_channel.c:352,680 nonce entropy
При постоянном провале random_bytes seed остаётся 0 (static init).
Nonce становится полностью предсказуемым (counter+timestamp).
36. route_bgp.c:1122 утечка памяти
route_bgp_send_nat_info: etcp_send без проверки → pkt/e утекают при провале.
37. route_bgp.c:1157 утечка памяти (аналогично #36)
route_bgp_send_nat_check_req: etcp_send без проверки.
38. config_parser.c:1055-1066 логическая ошибка
update_config_keys дописывает [global] секцию в конец файла.
При повторном вызове получается дубликат секции, парсер может сбойнуть.
========================================================================
LOW (13 проблем)
========================================================================
39. u_async.c:1594-1617 утечка (abort)
abort() в destroy до освобождения posted_tasks и мьютекса.
40. u_async.c:1608 race condition
posted_tasks_head читается без posted_lock в destroy.
41. memory_pool.c:47 truncation
size_t → uint32_t в u_calloc_impl (object_size > UINT32_MAX).
42. memory_pool.h:16 signedness
int free_count может обернуться при двойном освобождении.
43. timeout_heap.c:31 dead code
freed_count никогда не обновляется, timeout_heap_get_freed_count не реализован.
44. timeout_heap.c:66-67 integer overflow
capacity*2 и sizeof(TimeoutEntry)*new_cap могут переполниться.
45. ll_queue.c:106-127 dangling pointer
queue_free освобождает hash_table, но не чистит entry->hash_next.
46. pkt_normalizer.c:374 десинхронизация потока
При провале ll_alloc_lldgram 2 байта заголовка уже consumed,
но фрагмент сброшен → потеря данных.
47. eim_nat.c:105,181 unaligned access (ARM)
*(uint16_t*)(ip_data + 6) — невыровненный доступ на ARM.
48. secure_channel.c:108-131 избыточность
CRC32 поверх CCM (аутентифицированное шифрование) избыточен.
49. secure_channel.h:51-52 dead code
Неиспользуемые поля send_nonce[13]/recv_nonce[13].
50. config_parser.c:312 undefined behavior
~0U << (32 - cidr) при cidr==0 — сдвиг на ширину типа.
51. route_ping.c:227 integer overflow
uint16_t avg_rtt = timeout_ms * 10 — переполнение при timeout_ms > 6553.
========================================================================
Файлы без ошибок
========================================================================
- route_node.c — чисто
- tun_if.c — чисто
- etcp_api.c — только порядок queue_entry_free/dgram_free (LOW, см. etcp.c Issue #7 старого аудита)
- etcp_loadbalancer.c — не аудирован (250 строк)
========================================================================
Рекомендуемый порядок исправления
========================================================================
1. КРИТИЧЕСКИ ФУНКЦИОНАЛЬНЫЕ:
u_async.c #2 (posted_tasks не работают на Linux)
u_async.c #3 (утечки при сбое старта)
2. USE-AFTER-FREE:
etcp_connections.c #6,#7,#8,#5 (висячие таймеры)
u_async.c #1 (частичный realloc)
ll_queue.c #17 (reentrancy)
3. УТЕЧКИ ПАМЯТИ:
etcp.c #5 (dgram из data_pool)
route_bgp.c #14,#15,#36,#37
route_ping.c #16
4. OOB / NULL DEREF:
etcp_connections.c #9,#26 (INIT bounds)
pkt_normalizer.c #10,#11 (null deref)
5. ЛОГИКА:
eim_nat.c #12,#13 (порты + expiry)
memory_pool.c #4 (double-free)
config_parser.c #38 (дубликат секции)
+28
View File
@@ -0,0 +1,28 @@
# Bootstrap Makefile for utun
# All build artifacts (.o) go to build/ directory
# Final binary (utun) is copied to project root
BUILD_DIR = build
all:
@mkdir -p $(BUILD_DIR)
@if test ! -f $(BUILD_DIR)/config.status || test ./configure -nt $(BUILD_DIR)/config.status; then \
echo "Configuring in $(BUILD_DIR)/..."; \
cd $(BUILD_DIR) && ../configure; \
fi
@$(MAKE) -C $(BUILD_DIR)
clean:
-rm -rf $(BUILD_DIR)
distclean: clean
-rm -f config.status config.log config.h stamp-h1
-rm -rf autom4te.cache/
check: all
@$(MAKE) -C $(BUILD_DIR) check
install:
@$(MAKE) -C $(BUILD_DIR) install
.PHONY: all clean distclean check install
+1 -19
View File
@@ -1,26 +1,8 @@
# Main Makefile.am for utun - root directory configuration
# This is the root Makefile.am - tests are defined in tests/Makefile.am
# Basic configuration for root directory
# Order matters: lib -> src -> tests (tests depend on src objects)
SUBDIRS = lib src tests
# Build directory for out-of-tree builds
BUILD_DIR = build
# Custom target to run full build in separate directory
.PHONY: fullbuild
fullbuild:
@echo "Building in $(BUILD_DIR)/ directory..."
@$(MKDIR_P) $(BUILD_DIR)
@if test ! -f $(BUILD_DIR)/Makefile; then \
echo "Running configure in $(BUILD_DIR)..."; \
cd $(BUILD_DIR) && $(top_srcdir)/configure; \
fi
@$(MAKE) -C $(BUILD_DIR) all
@echo "Build complete. Binaries copied to project root."
# Clean build directory
clean-local:
-rm -rf $(BUILD_DIR)
-rm -rf build/
Regular → Executable
+13 -27
View File
@@ -69,17 +69,16 @@ fi
if [ "$CLEAN" -eq 1 ]; then
echo "Cleaning build..."
make clean 2>/dev/null || true
rm -f config.status config.h
fi
# Full rebuild: clean everything and regenerate
# Full rebuild: regenerate build system
if [ "$FULL_REBUILD" -eq 1 ]; then
echo ""
echo "======================================"
echo "FULL REBUILD: autoreconf + configure"
echo "FULL REBUILD: autoreconf"
echo "======================================"
echo ""
# Check for autotools
if ! command -v autoreconf &> /dev/null; then
echo "Error: autoreconf not found."
@@ -90,29 +89,24 @@ if [ "$FULL_REBUILD" -eq 1 ]; then
fi
exit 1
fi
# Clean generated files
echo "Cleaning generated files..."
make distclean 2>/dev/null || true
rm -f config.status config.h Makefile */Makefile
# Regenerate build system
rm -rf build/
# Regenerate build system (root Makefile is hand-written, not generated)
echo ""
echo "Running autoreconf -fi..."
autoreconf -fi 2>&1 | tee autoreconf.log
# Configure
echo ""
echo "Running configure..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
echo ""
echo "======================================"
echo "Build system regenerated successfully"
echo "======================================"
echo ""
else
# Quick/incremental build: only configure if needed
# Quick/incremental build: generate configure if needed
if [ ! -f ./configure ]; then
echo "Configure script not found. Generating..."
if ! command -v autoreconf &> /dev/null; then
@@ -126,21 +120,13 @@ else
fi
autoreconf -fiv
fi
# Run configure if needed
if [ ! -f config.status ] || [ config.status -ot configure ]; then
echo "Configuring build..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
else
echo "Using existing configuration."
fi
fi
# Build
# Build (all .o files go to build/ directory, binary copied to project root)
echo ""
echo "Compiling with $JOBS..."
BUILD_SUCCESS=0
make $JOBS $MAKE_ARGS 2>&1 | tee build_linux.log
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
if [ ${PIPESTATUS[0]} -eq 0 ]; then
BUILD_SUCCESS=1
fi
@@ -148,10 +134,10 @@ fi
if [ $BUILD_SUCCESS -eq 1 ]; then
echo ""
echo "Build completed successfully!"
echo "Binary: src/utun"
echo "Binary: utun"
else
echo ""
echo "ERROR: Build failed!"
echo "Check build_linux.log for details"
echo "Check build.log for details"
exit 1
fi
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
pkg install -y autoconf openssl
#pkg install -y autoconf openssl
mkdir -p m4
set -e
+137 -74
View File
@@ -7,120 +7,183 @@
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия".
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку.
как формируется пакет:
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку
1. выбираем канал передачи (etcp_loadbalancer_select_link)
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем
если пакет найден:
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
2. в конец добавляем секцию 0x00 с данными пакета
3. если пакет найден или есть неподтверждённые ACK:
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
2. в конец добавляем секцию 0x00 с данными пакета
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен
Функция прикрепления опциональных секций:
- запрашивает выбор канала передачи.
- добавляет накопившиеся ACK
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets)
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем).
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки.
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
пересканирование inflight:
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight.
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени)
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts).
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное]
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет.
- если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди
inflight - два списка (ll_queue):
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку)
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack)
доп. свойства пакетов:
- указатель на ETC_LINK последней отправки
- timestamp последней отправки
- число отправок
- число запросов переповтора
inflight - две очереди (ll_queue):
- input_send_q: список пакетов ожидающих отправку
- input_wait_ack: список неподтвержденных пакетов ожидающих ack
При отправке пакет перемещается из input_send_q в input_wait_ack.
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q.
доп. свойства пакетов (struct INFLIGHT_PACKET):
- указатель на ETC_LINK последней отправки (last_link)
- timestamp последней отправки (last_timestamp)
- число отправок (send_count)
- send_hist[8] - история через какие линки отправлялся (индекс линка)
- inflight_bytes/inflight_packets per-link отслеживается в ETCP_LINK
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight.
optimal_inflight расчитывается из RTT и bandwidth линка.
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом:
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась).
после передаём пачку пакетов только по этому линку со следующими таймингами:
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов.
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts].
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth.
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit.
bandwidth по каждому линку адаптивно подстраиваем:
Периодически в stats_timer_cb собираем статистику окна (rtt_avg10, ретрансмиссии, переданные пакеты).
BW рассчитывается как: inflight_lim_bytes * 8 / rtt_min (в секундах) * 8 / 1024 * 1.3 (Kbits/sec).
Размер inflight_lim_bytes плавно подстраивается вверх/вниз в зависимости от того, растёт ли RTT.
Burst-измерения bandwidth (0x07, 0x08) зарезервированы, но на данный момент не реализованы.
**** Формат кодограмм для etcp.c/h ****
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags]
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
- flags: bit0 = up/down (recv_keepalive)
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ
2. Опциональные секции (одна или несколько) добавляются в etcp.c:
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags/flag_up]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке).
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных.
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка):
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: количество пар ID+timestamp (1-32)
- last_delivered_id: последний ID, доставленный получателю
- last_rx_id: последний полученный ID (для синхронизации прогресса)
в) channel timestamp (добавляется после выбора канала передачи):
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low]
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно).
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами]
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count]
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета)
- last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд)
- rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне)
- seq: uint32_t — номер подтверждаемого пакета
- recv_ts: uint16_t — локальный timestamp приёма пакета (в единицах 0.1 мс)
- dly: uint16_t — задержка между приёмом пакета и отправкой ACK (для расчёта RTT)
Каждая запись = 8 байт.
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные):
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)]
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time)
(оценка текущего времени на передающей стороне в момент отправки)
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp
(относительное смещение между локальным и удалённым временем)
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки)
и если прошло не более ~100ms (dt < 1000000 таймбаз).
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID 4 байта] [данные...] - Данные пакета
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit]
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?]
[0x00] [ID 4 байта LE] [данные...]
- ID: uint32_t, 32-битный циклический номер пакета
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные),
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний.
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции).
цель протокола:
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT)
Установка подключения для канала (реализуется в etcp_connections.c/h):
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи).
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
- RTT последнего пакета и время отправки последнего пакета
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики:
- количество отправленных в пакетах байтах
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- общее количество отправленных/принятых байт
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент
Планировщик/балансир каналов (etcp_loadbalancer.c/h):
для каждого канала ограничение полосы с тестированием отклика:
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков)
Ограничение реализуем следующим образом:
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта).
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности.
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время)
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues]
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи");
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
процесс установки соединения:
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK)
- по таймауту повторяем init запросы, ведем счетчик запросов init.
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1.
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения).
процесс установки соединения (рукопожатие):
- клиент отправляет init запрос и выставляет таймаут
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive.
- сервер при получении init запроса: если линк существует и session_id совпал — отвечает без сброса (0x05); если session_id новый — выполняет etcp_conn_reinit и отвечает со сбросом (0x03).
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT со сбросом (0x02); если link_state=2 (reconnect) — без сброса (0x04).
link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
**** Формат кодограмм для etcp_connections.c/h ****
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат]
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса):
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [recovery high] [recovery low] [my link_id 1 байт] [my public key (64 байта, не шифруется)]
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы.
- link_id: локальный идентификатор канала (0-255), назначается отправителем для идентификации канала
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM)
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04):
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low] [my link_id 1 байт] [peer ipv4 4 байта] [peer port 2 байта]
- Подтверждение инициализации (канал успешно создан, можно начинать обмен)
- link_id: локальный идентификатор канала (0-255) отправителя ответа
- peer ip.port: возвращаем ip:port с которого пришел init запрос (чтобы клиент узнал свой external ip:port если за nat)
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда одна секция-тип пакета). В обязательном заголовке (timestamp+flag_up) ID не используется.
Типы пакетов:
0x02 — ETCP_INIT_REQUEST (со сбросом etcp сессии)
0x03 — ETCP_INIT_RESPONSE (подтверждение со сбросом)
0x04 — ETCP_INIT_REQUEST_NOINIT (без сброса)
0x05 — ETCP_INIT_RESPONSE_NOINIT (подтверждение без сброса)
0x06 — ETCP_PING (проверка доступности)
0x07 — ETCP_PONG (ответ на пинг)
1) Init запрос (0x02 / 0x04):
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [keepalive_interval(2)]
[recovery_interval/100(2)] [local_link_id(1)] [sock_id(1)] [only_local(1)] [type(1)]
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
- code: 0x02 (со сбросом) или 0x04 (без сброса)
- node_id: 64 бита, идентификатор узла (big-endian)
- session_id: 32 бита, случайный ID сессии генерируется клиентом (big-endian)
- mtu_local: uint16_t локальный MTU
- keepalive_interval: uint16_t интервал keepalive в мс
- recovery_interval: uint16_t = recovery_interval/100 (в таймбазах 0.1мс / 100)
- local_link_id: uint8_t (0-255), назначается отправителем
- sock_id: uint8_t, уникальный ID сокета отправителя
- only_local: uint8_t, флаг "только локальные подключения"
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
в noencrypt_len байтах (не попадает под AES-шифрование)
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт.
Между нодами только одно подключение, но можно добавлять каналы.
2) Init подтверждение (0x03 / 0x05):
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [local_link_id(1)]
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2)]
[random_padding...]
- code: 0x03 (со сбросом) или 0x05 (без сброса)
- node_id: 64 бита, big-endian
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует)
- mtu_local: uint16_t локальный MTU сервера
- local_link_id: uint8_t идентификатор канала сервера
- remote_socket_id: uint8_t (копируется из запроса)
- only_local: uint8_t флаг сервера
- type: uint8_t тип сокета сервера
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
- peer_port: 2 байта (порт клиента как его видит сервер)
- random_padding: случайные байты
3) PING (0x06):
[0x06] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...]
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
Отправляется с отдельным crypto-контекстом (не связан с ETCP-сессией).
Используется для проверки доступности узла и NAT-тестирования.
4) PONG (0x07):
[0x07] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...]
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
Ответ на PING. nonce копируется из запроса для сопоставления.
При получении init получатель пакета должен:
- reset ETCP_LINK с этим ip_port если он есть
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации)
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - проверить ключи на совпадение.
приём кодограмм из UDP выглядит так:
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
Keepalive: пакет без секций (только timestamp+flags)
Keepalive: пакет без секций (только timestamp+flag_up в шифрованном заголовке, data_len=0).
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика.
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента).
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты).
Клиент: если все линки =down то начинается процедура восстановления связи:
- клиент посылает init без сброса
- сервер как получает init без сброса отправляет init подтверждение: со сбросом или без сброса в зависимости от состояния init его соединения (инициализировано - то без сброса).
- клиент посылает init без сброса (0x04), link_state=2
- сервер как получает init проверяет session_id:
- если session_id совпадает — шлёт подтверждение без сброса (0x05)
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03)
+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 │
└──────────────────────────────────────────────────┘
```
+344
View File
@@ -0,0 +1,344 @@
Архитектура P2P Direct Connection (установка оптимального подключения между узлами)
Цель: в большой сети узлов каждый узел умеет находить быстрые подключения
к любому другому узлу — либо прямое (direct), либо через минимальное число
промежуточных узлов с минимальным суммарным RTT. Оба узла пробуют доступные
варианты пингом и выбирают лучший.
=== Что уже есть в коде (используем, не дублируем) ===
1. NODEINFO уже содержит поле tranzit_nodes + структуру NODEINFO_TRANZIT_NODE
(node_id, rtt, link_q) — "лучшие транзитные узлы, выбирается/обновляется узлом",
но пока не заполняется и не используется.
2. Пинг-инфраструктура: route_ping_send_req_addr (запрос удалённого пинга через
третий узел), route_ping_handle_resp (приём результата). Также есть
etcp_send_ping_to_socket — прямой пинг с конкретного сокета на конкретный адрес
с pubkey для шифрования.
3. NAT-детекция уже работает: проверка типа NAT через третий узел, обмен NAT_INFO.
4. Path system: NODEINFO_PATH с hop_count, routes → ROUTE_ENTRY → NODEINFO_Q → paths —
уже выбирается путь для маршрутизации пакетов.
5. Константа ROUTE_SUBCMD_CONN_REQ (0x0B) зарезервирована в route_bgp.h, но не реализована.
6. NODEINFO_Q уже имеет best_socket, last_ping_time, last_rtt.
=== Общий алгоритм (на примере узлов A и B) ===
A ──(ETCP через релейные узлы)──> B [текущий путь, hop_count > 1]
1. A замечает большой объём трафика к B → запускает CONN_REQ
2. A шлёт B через существующий ETCP-путь: свои адреса-кандидаты + лучших транзитных соседей
3. B получает CONN_REQ, сразу начинает пинговать адреса A, собирает свои данные
4. B шлёт CONN_RESP: свои адреса + транзитных соседей + результаты пробных пингов
5. A получает CONN_RESP, пингует все адреса B
6. Оба вычисляют лучший вариант (прямой или релейный с минимальным RTT)
7. Оба шлют друг другу CONN_RESULT с выбором
8. Оба пытаются создать ETCP link к выбранному адресу (если прямой)
9. Лишние/худшие линки закрываются позже — все линки в рамках одного ETCP_CONN
=== Фаза 1: Обмен кандидатами (CONN_REQ → CONN_RESP) ===
Узел-инициатор A собирает и отправляет:
- Свои адреса-кандидаты: из своих ETCP_SOCKET (interface_addr + nat_addr).
Включаются только сокеты с NAT_VERIFIED_* / EIM / PUBLIC (type >= NAT_VERIFIED_UNKNOWN).
Для каждого: ip, port, type, socket_id.
- Лучших транзитных соседей: top-N directly-connected узлов (hop_count == 1)
отсортированных по NODEINFO_Q.last_rtt (лучший RTT первым).
Узел B при получении CONN_REQ:
- Сохраняет кандидатов A
- Сразу запускает пробные пинги ко всем адресам A (etcp_send_ping_to_socket с pubkey)
- Собирает свои адреса-кандидаты
- Собирает своих транзитных соседей
- Шлёт CONN_RESP со своими данными + уже готовыми результатами проб
=== Фаза 2: Пробы (CONN_RESP → Пинги) ===
Узел A при получении CONN_RESP:
- Запускает пинги ко всем адресам B:
каждый адрес B пингуется с каждого своего сокета (N×M проб)
- Использует etcp_send_ping_to_socket с pubkey узла B (из NODEINFO)
- Пинг идёт напрямую по UDP (не через ETCP) — проверяет реальную достижимость
включая NAT traversal
- Ждёт все результаты либо таймаут
Параллельно B тоже завершает свои пробы (запущенные на шаге 3).
=== Фаза 3: Выбор лучшего пути (→ CONN_RESULT) ===
Каждая сторона независимо вычисляет:
best = {mode: none, rtt: 65535}
Прямые варианты:
for each (мой_сокет, peer_addr) где probe.ok:
if probe.rtt < best.rtt:
best = {direct, my_sock_idx, peer_addr_idx, probe.rtt}
Релейные варианты:
for each общий_транзитный_узел R (доступен и A и B):
// RTT(A↔R) берём из своих замеров (A знает RTT до своих соседей)
// RTT(R↔B) берём из транзитных соседей B (присланы в CONN_RESP)
total_rtt = A→R_rtt + R→B_rtt
if total_rtt < best.rtt:
best = {relay, R, total_rtt}
Обе стороны приходят к одинаковому выводу (информация симметрична).
Шлют CONN_RESULT с выбором.
Если прямой вариант — оба пытаются создать ETCP link к адресу пира.
Если релейный — используют существующий путь (уже работает).
=== Триггер запуска negotiation ===
В route_pkt (routing.c) при отправке пакета узлу с hop_count > 1:
- Накапливаем счётчик байт к этому узлу (per-node counter в bgp)
- При превышении порога (например 64KB) и если negotiation ещё не запущен — стартуем
- Cooldown: не чаще чем раз в 30 секунд для одной пары узлов
- Если negotiation уже в процессе для этой пары — не дублируем
=== Tranzit nodes — автоматическое заполнение ===
В route_bgp_update_my_nodeinfo (route_node.c) при каждом обновлении NODEINFO:
- Сканируем directly-connected соседей (из bgp->nodes, где hop_count == 1)
- Сортируем по NODEINFO_Q.last_rtt (лучший RTT первым)
- Берём top-N (до 4) и упаковываем в NODEINFO.tranzit_nodes как массив
NODEINFO_TRANZIT_NODE (node_id, rtt, link_q)
Плюс периодический refill (раз в ~10 сек) для свежести RTT замеров.
=== Структуры данных (новые, route_p2pconn.h) ===
#define P2P_MAX_ADDRS 8
#define P2P_MAX_RELAYS 8
#define P2P_MAX_PROBES (P2P_MAX_ADDRS * P2P_MAX_ADDRS) // до 64
#define P2P_PHASE_WAIT_RESP 0 // ждём CONN_RESP от пира
#define P2P_PHASE_PROBING 1 // пингуем адреса пира
#define P2P_PHASE_SELECTING 2 // выбор лучшего, отправка CONN_RESULT
#define P2P_PHASE_DONE 3 // завершено
// Один адрес-кандидат
struct P2P_ADDR {
uint32_t ip; // network byte order
uint16_t port;
uint8_t type; // NAT_VERIFIED_*
uint8_t socket_id;
};
// Один транзитный узел
struct P2P_RELAY {
uint64_t node_id;
uint16_t rtt; // x0.1ms
uint16_t link_q;
};
// Результат одного пробного пинга
struct P2P_PROBE_RESULT {
uint8_t my_sock_idx; // индекс в my_addrs[]
uint8_t peer_addr_idx; // индекс в peer_addrs[]
uint16_t rtt; // x0.1ms, 0 = fail
};
// Состояние одних переговоров (хранится в хеш-таблице bgp->p2p_negotiations)
struct P2P_NEGOTIATION {
struct ll_entry ll;
uint32_t request_id;
uint64_t peer_node_id;
uint8_t phase;
// Мои данные
struct P2P_ADDR my_addrs[P2P_MAX_ADDRS];
uint8_t my_addr_count;
struct P2P_RELAY my_relays[P2P_MAX_RELAYS];
uint8_t my_relay_count;
// Данные пира (заполняются из CONN_RESP)
struct P2P_ADDR peer_addrs[P2P_MAX_ADDRS];
uint8_t peer_addr_count;
struct P2P_RELAY peer_relays[P2P_MAX_RELAYS];
uint8_t peer_relay_count;
// Результаты проб
struct P2P_PROBE_RESULT probes[P2P_MAX_PROBES];
uint8_t probe_total; // сколько всего запланировано
uint8_t probe_done; // сколько завершилось (ok + fail)
// Лучший выбор
uint8_t best_mode; // 1=direct, 2=relay
uint64_t best_relay; // node_id релея (если mode=relay)
uint16_t best_rtt; // x0.1ms
uint8_t best_my_sock_idx; // индекс в my_addrs (если direct)
uint8_t best_peer_addr_idx;// индекс в peer_addrs (если direct)
void* timeout_timer; // общий таймаут на всю negotiation
};
// Счётчик трафика per-node (для триггера, хранится в bgp)
struct P2P_TRAFFIC_COUNTER {
uint64_t node_id;
uint64_t bytes_sent; // накоплено байт
uint64_t last_negotiation_time; // время последней попытки (0 = не было)
};
=== Протокольные пакеты (добавляются в route_bgp.h) ===
ROUTE_SUBCMD_CONN_REQ 0x0B // запрос прямого подключения (уже зарезервирован)
ROUTE_SUBCMD_CONN_RESP 0x0C // ответ с адресами + результаты проб
ROUTE_SUBCMD_CONN_RESULT 0x0E // финальный выбор
// Пакет CONN_REQ (A → B)
struct BGP_CONN_REQ {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_CONN_REQ
uint32_t request_id; // для корреляции
uint8_t addr_count; // число адресов-кандидатов
uint8_t relay_count; // число транзитных узлов
uint8_t reserved[2];
// далее динамически (размер = addr_count*8 + relay_count*12):
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
};
// Пакет CONN_RESP (B → A)
struct BGP_CONN_RESP {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESP
uint32_t request_id;
uint8_t addr_count; // адреса B
uint8_t relay_count; // транзитные узлы B
uint8_t probe_count; // готовые результаты проб B→A
uint8_t reserved;
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
// [probe_count × {peer_addr_idx[1] my_sock_idx[1] rtt[2] ok[1]}]
};
// Пакет CONN_RESULT (A ↔ B, финальный)
struct BGP_CONN_RESULT {
uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESULT
uint32_t request_id;
uint8_t chosen_mode; // 1=direct, 2=relay
uint8_t reserved;
uint16_t chosen_rtt; // x0.1ms
// Если direct:
uint32_t peer_ip; // IP пира к которому подключаемся
uint16_t peer_port;
uint8_t my_socket_id; // свой сокет для подключения
uint8_t peer_socket_id; // сокет пира
// Если relay (оверлей тех же байт):
// uint64_t relay_node_id;
};
=== API модуля route_p2pconn ===
// Запуск negotiation к узлу peer_node_id.
// Вызывается из route_pkt при превышении порога трафика.
int p2p_start_negotiation(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
// Проверка: запущена ли уже negotiation для этой пары
int p2p_is_negotiating(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
// Сбор своих адресов-кандидатов (из etcp_sockets)
int p2p_collect_my_addrs(struct ROUTE_BGP* bgp, struct P2P_ADDR* out, uint8_t max);
// Сбор лучших транзитных соседей (из directly-connected nodes)
int p2p_collect_my_relays(struct ROUTE_BGP* bgp, struct P2P_RELAY* out, uint8_t max);
// Обработчики входящих пакетов (вызываются из route_bgp_receive_cbk):
void p2p_handle_conn_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
void p2p_handle_conn_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
void p2p_handle_conn_result(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
// Отмена negotiation при удалении conn
void p2p_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
// Очистка всех negotiation (при destroy bgp)
void p2p_destroy_all(struct ROUTE_BGP* bgp);
=== Схема состояний negotiation ===
IDLE ──(трафик > порог)──→ WAIT_RESP ──(CONN_RESP получен)──→ PROBING
↑ ↑ │
│ (таймаут) (все пробы готовы)
│ ↓ │
└──────────────────────────── DONE ←──(CONN_RESULT отправлен)── SELECTING
=== Интеграция (какие файлы меняются) ===
Новые файлы:
src/route_p2pconn.h — структуры, константы, прототипы
src/route_p2pconn.c — вся логика negotiation
Изменения в существующих:
src/route_bgp.h — добавить ROUTE_SUBCMD_CONN_RESP 0x0C,
ROUTE_SUBCMD_CONN_RESULT 0x0E,
структуры пакетов BGP_CONN_REQ/RESP/RESULT
src/route_bgp.c — в route_bgp_receive_cbk добавить обработку
новых subcmd. В struct ROUTE_BGP добавить
поле p2p_negotiations (очередь/хеш negotiation)
и p2p_traffic_counters (per-node counters)
src/route_node.c — в route_bgp_update_my_nodeinfo заполнять
tranzit_nodes из RTT directly-connected соседей
src/routing.c — в route_pkt добавить накопление счётчика трафика
и вызов p2p_start_negotiation при превышении порога
src/Makefile.am — добавить route_p2pconn.c в сборку
=== Внутренняя логика route_p2pconn.c ===
p2p_start_negotiation(bgp, peer_node_id):
1. Проверить что нет активной negotiation для этой пары
2. Проверить cooldown (30 сек)
3. Создать struct P2P_NEGOTIATION, заполнить my_addrs, my_relays
4. Сформировать BGP_CONN_REQ и отправить через существующий путь к пиру
5. Поставить таймаут на всю negotiation (например 5 сек)
p2p_handle_conn_req(bgp, from_conn, data, len):
1. Распарсить BGP_CONN_REQ
2. Сохранить адреса и релеи инициатора в P2P_NEGOTIATION
3. Собрать свои my_addrs, my_relays
4. Запустить пробные пинги к адресам инициатора (p2p_start_probes)
5. Когда пробы готовы — отправить CONN_RESP с результатами
p2p_start_probes(neg):
1. Для каждой пары (мой_сокет, peer_addr) запланировать пинг
2. Вызвать etcp_send_ping_to_socket с pubkey пира
3. В коллбэке сохранить результат в probes[], инкрементировать probe_done
4. Когда probe_done == probe_total → вызвать p2p_select_best
p2p_select_best(neg):
1. Прямые: найти пару (my_sock, peer_addr) с минимальным rtt > 0
2. Релейные: для каждого общего транзитного узла посчитать суммарный rtt,
найти минимум
3. Сравнить прямой vs релейный лучший rtt
4. Записать выбор в neg->best_*
5. Отправить CONN_RESULT пиру
6. Вызвать p2p_establish_link(neg)
p2p_establish_link(neg):
1. Если best_mode == direct:
- Найти/создать ETCP_CONN к пиру (по node_id)
- Вызвать etcp_link_new с chosen address
2. Если best_mode == relay:
- Ничего не делаем, существующий путь уже работает
3. Пометить negotiation как DONE
+11 -1
View File
@@ -17,8 +17,18 @@ libuasync_a_SOURCES = \
socket_compat.h \
platform_compat.c \
platform_compat.h \
getmyip.c \
getmyip.h \
swm_min.c \
swm_min.h \
mem.c \
mem.h
mem.h \
radix.c \
radix.h \
serialize.c \
serialize.h \
tcp_io.c \
tcp_io.h
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
+62
View File
@@ -57,6 +57,49 @@ ip_str_t ip_to_str(const void *addr, int family) {
return result;
}
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr) {
ip_str_t result;
result.str[0] = '\0';
if (!addr) {
return result;
}
char ip_buf[INET6_ADDRSTRLEN];
ip_buf[0] = '\0';
uint16_t port = 0;
void *src = NULL;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
src = (void *)&ipv4->sin_addr;
port = ntohs(ipv4->sin_port);
}
else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
src = (void *)&ipv6->sin6_addr;
port = ntohs(ipv6->sin6_port);
}
else {
return result;
}
if (!inet_ntop(addr->ss_family, src, ip_buf, sizeof(ip_buf))) {
return result;
}
if (addr->ss_family == AF_INET6) {
// IPv6 обязательно в []
snprintf(result.str, sizeof(result.str), "[%s]:%u", ip_buf, port);
} else {
snprintf(result.str, sizeof(result.str), "%s:%u", ip_buf, port);
}
return result;
}
static const struct {
const char* name;
@@ -82,6 +125,9 @@ static const struct {
{"traffic", DEBUG_CATEGORY_TRAFFIC},
{"debug", DEBUG_CATEGORY_DEBUG},
{"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}
};
@@ -173,6 +219,9 @@ const char* debug_get_category_name(debug_category_t category_idx) {
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
case DEBUG_CATEGORY_NAT: return "NAT";
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
case DEBUG_CATEGORY_ETCPROUTE: return "ETCPROUTE";
case DEBUG_CATEGORY_NONE: return "NONE";
default: return "UNKNOWN";
}
@@ -297,6 +346,19 @@ void debug_disable_file_output(void) {
}
}
/* Reopen log file (append mode) for SIGHUP. Complete operation with full error logging. */
int debug_reopen_log(void) {
if (!g_debug_config.output_file) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "No log path for reopen");
return -1;
}
const char* path = g_debug_config.output_file;
debug_disable_file_output();
debug_enable_file_output(path, 0); // append
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Log reopened: %s", path);
return 0;
}
/* Check if debug output should be shown for given level and category */
int debug_should_output(debug_level_t level, debug_category_t category_idx) {
if (category_idx < 0 || category_idx >= DEBUG_CATEGORY_COUNT) {
+17 -9
View File
@@ -6,9 +6,11 @@
#ifndef DEBUG_CONFIG_H
#define DEBUG_CONFIG_H
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "platform_compat.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h> // struct sockaddr_storage
#endif
@@ -55,7 +57,10 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_TRAFFIC 17 // Traffic flow (src/dst node IDs)
#define DEBUG_CATEGORY_DEBUG 18 // Current debugging
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
#define DEBUG_CATEGORY_COUNT 20 // Total number of categories
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_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 */
@@ -103,16 +108,19 @@ void debug_set_output_file(const char* file_path);
/* Dual output configuration */
void debug_set_console_level(debug_level_t level);
void debug_set_file_level(debug_level_t level);
void debug_enable_file_output(const char* file_path, int truncate);
void debug_disable_file_output(void);
void debug_enable_console(int enable);
void debug_enable_file_output(const char* file_path, int truncate);
void debug_disable_file_output(void);
int debug_reopen_log(void);
void debug_enable_console(int enable);
// IP address to string (static buffer, single-threaded)
typedef struct {
char str[48]; // INET6_ADDRSTRLEN(45) + \0
char str[54]; // INET6_ADDRSTRLEN(45) + 6chars (:port) + \0
} ip_str_t;
ip_str_t ip_to_str(const void *addr, int family);
ip_str_t ip_to_str(const void *addr, int family);// big endian addr
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
// hex dump в лог
void log_dump(const char* prefix, const uint8_t* data, size_t len);
+102
View File
@@ -0,0 +1,102 @@
/**
* getmyip.c - Определение локального IP-адреса для исходящих соединений
*
* Использует временный UDP-сокет с connect() для определения
* локального IP, который ОС выберет для заданного маршрута.
*/
#include "getmyip.h"
#include "socket_compat.h"
#include "debug_config.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#endif
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
struct sockaddr_storage *result)
{
if (!local_addr || !remote_addr || !result) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument");
return -1;
}
int family = local_addr->ss_family;
if (family != AF_INET && family != AF_INET6) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: unsupported family %d", family);
return -1;
}
if (remote_addr->ss_family != family) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch");
return -1;
}
socket_t sock = socket_create_udp(family);
if (sock == SOCKET_INVALID) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed");
return -1;
}
// Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
struct sockaddr_storage bind_addr;
memcpy(&bind_addr, local_addr, sizeof(bind_addr));
if (family == AF_INET) {
((struct sockaddr_in *)&bind_addr)->sin_port = 0;
} else {
((struct sockaddr_in6 *)&bind_addr)->sin6_port = 0;
}
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (bind(sock, (struct sockaddr *)&bind_addr, bind_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
// Connect к remote_addr — ядро вычислит маршрут и выберет source IP
socklen_t remote_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (connect(sock, (struct sockaddr *)remote_addr, remote_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
// Получаем выбранный локальный адрес
struct sockaddr_storage local;
socklen_t local_len = sizeof(local);
if (getsockname(sock, (struct sockaddr *)&local, &local_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
socket_close_wrapper(sock);
// Копируем только IP (без порта)
memset(result, 0, sizeof(*result));
if (family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)result;
struct sockaddr_in *src = (struct sockaddr_in *)&local;
sin->sin_family = AF_INET;
sin->sin_addr = src->sin_addr;
sin->sin_port = 0;
} else {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)result;
struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&local;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = src6->sin6_addr;
sin6->sin6_port = 0;
}
return 0;
}
+45
View File
@@ -0,0 +1,45 @@
/**
* getmyip.h - Определение локального IP-адреса для исходящих соединений
*
* Определяет локальный IP, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*/
#ifndef GETMYIP_H
#define GETMYIP_H
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Определяет локальный IP-адрес, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*
* Создает временный UDP-сокет, выполняет bind на local_addr,
* connect к remote_addr и получает выбранный локальный IP через getsockname.
*
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения)
* @param remote_addr Удаленный адрес (для определения маршрута)
* @param result Заполняемый локальный IP (порт = 0)
* @return 0 при успехе, -1 при ошибке
*/
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
struct sockaddr_storage *result);
#ifdef __cplusplus
}
#endif
#endif
+225 -77
View File
@@ -46,12 +46,27 @@ 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);
}
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name) {
if (!ua) return NULL;
struct ll_queue* q = u_calloc(1, sizeof(struct ll_queue));
@@ -60,7 +75,12 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
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;
#ifdef QUEUE_THREAD_CHECK
#ifdef _WIN32
@@ -110,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);
@@ -120,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);
}
@@ -225,8 +264,8 @@ void queue_entry_free(struct ll_entry* entry) {
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return;
if (q->hash_size == 0 || entry->index_size == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, entry->index_size);
if (q->hash_size == 0 || q->index_size == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, q->index_size);
return;
}
@@ -264,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);
}
}
@@ -291,9 +350,11 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
entry->index_offset = 0;
entry->index_size = 0;
entry->index_hash = 0;
if (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);
@@ -330,23 +391,26 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
return 0;
}
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
#endif
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
if (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);
queue_entry_free(entry);
return -1;
}
entry->index_offset = index_offset;
entry->index_size = index_size;
entry->index_hash = make_hash(entry->data + index_offset, index_size);
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
@@ -382,18 +446,14 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
entry->index_offset = 0;
entry->index_size = 0;
entry->index_hash = 0;
if (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);
queue_entry_free(entry);
return -1;
}
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
queue_entry_free(entry); // как было в оригинале
return -1;
@@ -419,22 +479,26 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
}
// С хешем для put_first
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
#endif
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
if (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);
queue_entry_free(entry);
return -1;
}
entry->index_offset = index_offset;
entry->index_size = index_size;
entry->index_hash = make_hash(entry->data + index_offset, index_size);
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_dgram_free(entry);
@@ -493,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
@@ -506,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;
@@ -524,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;
@@ -549,50 +631,116 @@ 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 ====================
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key, uint16_t index_size) {
if (!q || q->hash_size == 0 || !q->hash_table || index_size == 0 || !index_key) return NULL;
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key) {
if (!q || q->hash_size == 0 || !q->hash_table || q->index_size == 0 || !index_key) return NULL;
uint32_t hash_val = make_hash(index_key, index_size);
uint32_t hash_val = make_hash(index_key, q->index_size);
uint32_t slot = hash_val % q->hash_size;
struct ll_entry* entry = q->hash_table[slot];
while (entry) {
if (entry->index_size == index_size &&
(size_t)entry->index_offset + index_size <= (size_t)entry->size &&
memcmp(entry->data + entry->index_offset, index_key, index_size) == 0) {
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
return entry;
}
entry = entry->hash_next;
}
return NULL;
}
struct ll_entry* queue_find_next_by_index(struct ll_queue* q, const void* index_key,
struct ll_entry* prev_entry) {
if (!q || q->hash_size == 0 || !index_key || q->index_size == 0 || !prev_entry) return NULL;
struct ll_entry* entry = prev_entry->hash_next;
while (entry) {
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
return entry;
}
entry = entry->hash_next;
+111 -39
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
@@ -63,8 +65,6 @@ struct ll_entry {
uint8_t* dgram; // Указатель на данные пакета
void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram
struct memory_pool* dgram_pool; // Пул для dgram (если используется)
uint16_t index_offset; // Смещение индекса в data[]
uint16_t index_size; // Длина индекса (0 = без индекса)
uint32_t index_hash; // Хеш по первым 4 байтам индекса
struct ll_entry* hash_next; // Следующий в хеш-цепочке
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc)
@@ -86,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 пользовательский аргумент
*/
@@ -94,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;
};
/**
@@ -122,10 +138,23 @@ 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; // Размер хеш-таблицы
uint16_t index_offset; // Смещение индекса в data[] (для всех entry очереди)
uint16_t index_size; // Размер индекса (0 = без индекса)
#ifdef QUEUE_THREAD_CHECK
#ifdef _WIN32
@@ -142,9 +171,12 @@ struct ll_queue {
* @brief Создаёт новую пустую очередь.
* @param ua экземпляр uasync (обязателен для отложенного resume коллбэков)
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
* @param index_offset смещение ключа индекса в data[] каждого entry (0 если hash_size=0)
* @param index_size размер ключа индекса (0 если hash_size=0)
* @param name имя очереди для отладки
* @return указатель на очередь или NULL при ошибке выделения памяти
*/
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name);
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name);
/**
* @brief Освобождает структуру очереди.
@@ -187,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);
/* ==================== Работа с данными ==================== */
@@ -229,26 +297,20 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с явным указанием смещения индекса для hash.
* @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса (node_id) в data[]
* @param index_size размер индекса (8 для uint64_t node_id)
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди с явным указанием смещения индекса для hash.
* @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса в data[]
* @param index_size размер индекса
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
@@ -311,14 +373,24 @@ void queue_dgram_free(struct ll_entry* entry);
/* ==================== Поиск и удаление ==================== */
/**
* @brief Находит элемент по идентификатору (требуется hash_size > 0 при создании).
* @param q очередь
* @param id искомый идентификатор
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @return элемент или NULL
*/
struct ll_entry* queue_find_data_by_index(struct ll_queue* q,
const void* index_key,
uint16_t index_size);
const void* index_key);
/**
* @brief Находит следующий элемент с тем же ключом (продолжение поиска).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @param prev_entry предыдущий найденный элемент (от queue_find_data_by_index или предыдущего queue_find_next_by_index)
* @return следующий элемент с тем же ключом или NULL
*/
struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
const void* index_key,
struct ll_entry* prev_entry);
/**
* @brief Удаляет элемент из очереди (не освобождает память).
@@ -336,7 +408,7 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
* @return количество байт
*/
static inline size_t queue_total_bytes(struct ll_queue* q) {
return q ? q->total_bytes : 0;
return q->total_bytes;
}
#endif // LL_QUEUE_H
+76 -44
View File
@@ -14,13 +14,14 @@
#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>
#include <pthread.h>
#define BACKTRACE_ENABLED 1
#define BACKTRACE_ENABLED 0
#elif defined(_WIN32)
#include <windows.h>
#include <dbghelp.h>
@@ -50,28 +51,31 @@ static void init_mutex() {
static void* allocated_head = NULL;
static size_t allocated_count = 0;
static size_t allocations_count = 0;
static size_t frees_count = 0;
static void hex_dump(const void* mem, size_t len) {
const uint8_t* data = (const uint8_t*)mem;
char line[128];
for (size_t i = 0; i < len; i += 16) {
printf("%08zx: ", i);
int off = snprintf(line, sizeof(line), "%08zx: ", i);
for (size_t j = 0; j < 16; j++) {
if (i + j < len) {
printf("%02x ", data[i + j]);
off += snprintf(line + off, sizeof(line) - off, "%02x ", data[i + j]);
} else {
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
}
}
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
for (size_t j = 0; j < 16; j++) {
if (i + j < len) {
uint8_t c = data[i + j];
printf("%c", (c >= 32 && c <= 126) ? c : '.');
off += snprintf(line + off, sizeof(line) - off, "%c", (c >= 32 && c <= 126) ? c : '.');
} else {
printf(" ");
off += snprintf(line + off, sizeof(line) - off, " ");
}
}
printf("\n");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s", line);
}
}
@@ -87,34 +91,30 @@ void u_check(void* ptr, const char* text, const char* location) {
bool error = false;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
if (prefix_start[i] != PADDING_FILL) {
printf("%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
error = true;
}
}
if (*pre_canary != CANARY) {
printf("%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *pre_canary);
error = true;
}
uint8_t* post_start = user + size + 4;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
if (post_start[i] != PADDING_FILL) {
printf("%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, post_start[i]);
error = true;
}
}
uint32_t* post_canary = (uint32_t*)(user + size);
if (*post_canary != CANARY) {
printf("%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *post_canary);
error = true;
}
if (error) {
printf("%s: %s: Allocated at: %s\n", location, text, alloc_loc);
printf("%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes\n", location, text, size, total);
printf("%s: %s: Full memory dump at base %p:\n", location, text, base);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Allocated at: %s", location, text, alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Full memory dump at base %p:", location, text, base);
hex_dump(base, total);
exit(EXIT_FAILURE);
}
@@ -137,10 +137,15 @@ 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++;
UNLOCK();
return prefix_start + BOUNDARY_CHECK_SIZE + 8;
}
@@ -173,30 +178,48 @@ void u_free_impl(void* ptr, const char* location) {
if (!ptr) return;
u_check(ptr, "u_free", location);
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
// Remove from linked list (thread-safe)
char* alloc_loc = (char*)base;
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
// 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--;
found = true;
break;
void* prev = *(void**)(base + PREV_OFFSET);
void* next = *(void**)(base + NEXT_OFFSET);
size_t active_count = allocated_count;
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, "========================================");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Free attempt at: %s", location);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Pointer: %p", ptr);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Base: %p", (void*)base);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Allocated at: %s", alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "User size: %u bytes", size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Active allocs: %zu", active_count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8));
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8);
if (size > 0 && size <= 1024 * 1024) {
uint32_t dump_size = (size > 256) ? 256 : size;
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
hex_dump(ptr, dump_size);
}
prev = curr;
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
UNLOCK();
if (!found) {
printf("%s: u_free: Attempt to free unallocated or already freed pointer %p\n", location, ptr);
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);
}
@@ -212,12 +235,15 @@ void u_report_unfreed_blocks(void) {
LOCK();
size_t count = allocated_count;
void* head = allocated_head;
size_t alloc_count = allocations_count;
size_t free_count = frees_count;
UNLOCK();
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory stats: allocations=%zu, frees=%zu, current=%zu", alloc_count, free_count, count);
if (count == 0) {
printf("No unfreed memory blocks.\n");
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "No unfreed memory blocks.");
return;
}
printf("Unfreed memory blocks: %zu\n", count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Unfreed memory blocks: %zu, allocations: %zu frees: %zu", count, allocations_count, frees_count);
void* curr = head;
while (curr) {
char* alloc_loc = (char*)curr;
@@ -225,7 +251,13 @@ void u_report_unfreed_blocks(void) {
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4);
void* user_ptr = prefix_start + BOUNDARY_CHECK_SIZE + 8;
u_check(user_ptr, "unfreed_check", "u_report_unfreed_blocks");
printf(" - Allocated at: %s, size: %u bytes\n", alloc_loc, size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " - Allocated at: %s, size: %u bytes", alloc_loc, size);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " Full memory dump at %p:", user_ptr);
hex_dump(user_ptr, size);
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
}
size_t u_get_allocated_count(void) {
return allocated_count;
}
+1
View File
@@ -24,6 +24,7 @@ void* u_realloc_impl(void* ptr, uint32_t size, const char* location);
void u_free_impl(void* ptr, const char* location);
char* u_strdup_impl(const char* s, const char* location);
void u_report_unfreed_blocks(void);
size_t u_get_allocated_count(void);
#ifdef __cplusplus
}
#endif
+73 -9
View File
@@ -5,6 +5,43 @@
#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) {
return g_total_free;
}
// Инициализировать пул памяти
struct memory_pool* memory_pool_init(size_t object_size) {
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
@@ -16,44 +53,54 @@ 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;
}
// Выделить объект из пула или из malloc
void* memory_pool_alloc(struct memory_pool* pool) {
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
if (!pool) {
return NULL;
}
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++;
return u_calloc(1, pool->object_size); // Используем calloc для инициализации нулями
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
if (obj) pool_init_tags(pool, obj);
return obj;
}
// Освободить объект в пул или в free
void memory_pool_free(struct memory_pool* pool, void* obj) {
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location) {
if (!pool || !obj) {
return;
}
// Если пул не заполнен, сохранить объект для повторного использования
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++;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free returned: %s, free_count=%zu", location, pool->free_count);
return;
}
// Иначе освободить через free
u_free(obj);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
u_free_impl(obj, location);
}
// Получить статистику пула
@@ -75,15 +122,32 @@ void memory_pool_destroy(struct memory_pool* pool) {
return;
}
int cnt=0;
// Освободить все объекты в пуле
void* obj = pool->free_head;
while (obj) {
void* next = *(void**)obj;
g_total_free--;
u_free(obj);
cnt++;
obj = next;
}
// if (cnt>MEMORY_POOL_MAX_FREE)
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
pool->free_head = NULL;
pool->free_count = 0;
u_free(pool);
}
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;
}
+20 -8
View File
@@ -3,15 +3,21 @@
#define MEMORY_POOL_H
#include <stddef.h>
#include "debug_config.h"
#define MEMORY_POOL_MAX_FREE 64 // Максимальное количество свободных объектов в пуле (чтобы не накапливать слишком много)
#define MEMORY_POOL_MAX_FREE 64
#define XSTR(s) STR(s)
#define STR(s) #s
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
struct memory_pool {
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
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 байт)
};
// сам пул:
@@ -20,8 +26,14 @@ struct memory_pool* memory_pool_init(size_t object_size);
void memory_pool_destroy(struct memory_pool* pool);
// элементы пула:
void* memory_pool_alloc(struct memory_pool* pool);
void memory_pool_free(struct memory_pool* pool, void* obj);
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location);
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location);
void memory_pool_get_stats(struct memory_pool* pool, size_t* allocations, size_t* reuse_count);
size_t memory_pool_get_total_free_blocks(void);
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
#endif // MEMORY_POOL_H
+236
View File
@@ -0,0 +1,236 @@
/* outgoing_iface.c
*
* Определяет IPv4-адрес и индекс/имя исходящего интерфейса,
* который ОС выберет при отправке на заданный destination
* после bind на 0.0.0.0 (INADDR_ANY).
*
* Сборка:
* Linux / BSD: gcc -o out out.c
* Windows (MSVC): cl out.c /link ws2_32.lib iphlpapi.lib
* Windows (MinGW): gcc -o out.exe out.c -lws2_32 -liphlpapi
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ===================== Платформенные заголовки ===================== */
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <winsock2.h>
# include <ws2tcpip.h>
# include <iphlpapi.h>
# pragma comment(lib, "ws2_32.lib")
# pragma comment(lib, "iphlpapi.lib")
typedef SOCKET socket_t;
# define SOCKET_INVALID INVALID_SOCKET
# define SOCKET_ERRNO WSAGetLastError()
/* На Windows нет IFNAMSIZ; берём с запасом */
# ifndef IF_NAMESIZE
# define IF_NAMESIZE 256
# endif
#else /* Linux, FreeBSD, OpenBSD, NetBSD, macOS, … */
# include <unistd.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <ifaddrs.h>
# include <net/if.h>
# include <errno.h>
typedef int socket_t;
# define SOCKET_INVALID (-1)
# define SOCKET_ERRNO errno
# ifndef IF_NAMESIZE
# define IF_NAMESIZE IFNAMSIZ /* обычно 16 */
# endif
#endif
/* ===================== Структура результата ===================== */
typedef struct {
char ip[INET_ADDRSTRLEN]; /* IP строкой, например "192.168.1.5" */
char name[IF_NAMESIZE]; /* имя интерфейса: "eth0", "em0", "Wi-Fi" … */
uint32_t ifindex; /* числовой ifindex (if_nametoindex) */
} iface_info_t;
/* ===================== Утилиты ===================== */
static int net_init(void)
{
#ifdef _WIN32
static int done = 0;
if (!done) {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
done = 1;
}
#endif
return 0;
}
static void socket_close(socket_t s)
{
#ifdef _WIN32
if (s != SOCKET_INVALID)
closesocket(s);
#else
if (s >= 0)
close(s);
#endif
}
/* ===================== Основная функция ===================== */
/*
* dst_ip — адрес назначения (например, "8.8.8.8" или IP вашего VPN-сервера)
* dst_port — любой открытый порт (например, 53 или 123), реальных пакетов не уйдёт
* info — заполняемая структура
*
* Возвращает 0 при успехе, -1 при ошибке.
*/
int get_outgoing_iface(const char *dst_ip, int dst_port, iface_info_t *info)
{
socket_t sock = SOCKET_INVALID;
int ret = -1;
struct sockaddr_in dst, src;
socklen_t src_len = sizeof(src);
memset(info, 0, sizeof(*info));
if (net_init() != 0)
return -1;
/* 1. UDP-сокет */
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == SOCKET_INVALID)
return -1;
/* 2. Bind на 0.0.0.0 — именно так, как в вопросе */
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_addr.s_addr = INADDR_ANY; /* 0.0.0.0 */
dst.sin_port = 0; /* система выберет порт */
if (bind(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
goto end;
/* 3. "Соединяемся" с destination.
* Для UDP это НЕ шлёт реальных пакетов, но ядро просчитывает
* маршрут и фиксирует выбранный source IP в сокете. */
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_port = htons((uint16_t)dst_port);
if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1)
goto end;
if (connect(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
goto end;
/* 4. Забираем выбранный локальный адрес */
if (getsockname(sock, (struct sockaddr *)&src, &src_len) != 0)
goto end;
if (!inet_ntop(AF_INET, &src.sin_addr, info->ip, sizeof(info->ip)))
goto end;
/* 5. По source IP ищем интерфейс в системном списке */
#ifdef _WIN32
{
ULONG size = 0;
DWORD rv;
PIP_ADAPTER_ADDRESSES adapters = NULL, a;
PIP_ADAPTER_UNICAST_ADDRESS u;
/* первый вызов — узнать размер буфера */
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
NULL, NULL, &size);
if (rv != ERROR_BUFFER_OVERFLOW)
goto end;
adapters = (PIP_ADAPTER_ADDRESSES)malloc(size);
if (!adapters)
goto end;
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
NULL, adapters, &size);
if (rv != NO_ERROR) {
free(adapters);
goto end;
}
for (a = adapters; a != NULL; a = a->Next) {
for (u = a->FirstUnicastAddress; u; u = u->Next) {
struct sockaddr_in *ua =
(struct sockaddr_in *)u->Address.lpSockaddr;
if (ua->sin_addr.s_addr == src.sin_addr.s_addr) {
info->ifindex = (uint32_t)a->IfIndex;
/* FriendlyName — WCHAR, приводим к UTF-8 */
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1,
info->name, IF_NAMESIZE,
NULL, NULL);
ret = 0;
goto win_done;
}
}
}
win_done:
free(adapters);
}
#else /* Linux / BSD / macOS */
{
struct ifaddrs *ifap, *ifa;
if (getifaddrs(&ifap) != 0)
goto end;
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
continue;
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
if (addr->sin_addr.s_addr == src.sin_addr.s_addr) {
strncpy(info->name, ifa->ifa_name, IF_NAMESIZE - 1);
info->ifindex = if_nametoindex(ifa->ifa_name);
ret = 0;
break;
}
}
freeifaddrs(ifap);
}
#endif
end:
socket_close(sock);
return ret;
}
/* ===================== Пример использования =====================
int main(int argc, char **argv)
{
iface_info_t info;
const char *dst = (argc > 1) ? argv[1] : "8.8.8.8";
int port = (argc > 2) ? atoi(argv[2]) : 53;
if (get_outgoing_iface(dst, port, &info) == 0) {
printf("Destination : %s:%d\n", dst, port);
printf("Source IP : %s\n", info.ip);
printf("Iface name : %s\n", info.name);
printf("Iface index : %u\n", info.ifindex);
return 0;
}
fprintf(stderr, "Failed to determine outgoing interface (errno=%d)\n",
#ifdef _WIN32
(int)WSAGetLastError()
#else
errno
#endif
);
return 1;
}
*/
+127 -67
View File
@@ -24,6 +24,9 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#ifndef IFA_F_TEMPORARY
#define IFA_F_TEMPORARY 0x01
#endif
#endif
/*
@@ -48,49 +51,16 @@ int random_bytes(uint8_t *buffer, size_t len) {
}
#ifndef _WIN32
static uint32_t get_default_route_ip_linux(void) {
FILE *f = fopen("/proc/net/route", "r");
if (!f) return 0;
char line[256];
char *ifname = NULL;
unsigned int dest, gw;
if (!fgets(line, sizeof(line), f)) {
fclose(f);
return 0;
}
while (fgets(line, sizeof(line), f)) {
int items = sscanf(line, "%ms %x %x", &ifname, &dest, &gw);
if (items != 3) {
free(ifname);
continue;
}
if (dest == 0) {
free(ifname);
break;
}
free(ifname);
ifname = NULL;
}
fclose(f);
if (!ifname) return 0;
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
free(ifname);
return 0;
}
if (getifaddrs(&ifaddr) == -1) return 0;
uint32_t result = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
if (strcmp(ifa->ifa_name, ifname) != 0) continue;
unsigned int idx = if_nametoindex(ifa->ifa_name);
if (idx != netif_index) continue;
if (ifa->ifa_addr->sa_family != AF_INET) continue;
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
@@ -99,40 +69,130 @@ static uint32_t get_default_route_ip_linux(void) {
}
freeifaddrs(ifaddr);
free(ifname);
return result;
}
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
if (!addr_out) return -1;
memset(addr_out, 0, 16);
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) return -1;
int found = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
unsigned int idx = if_nametoindex(ifa->ifa_name);
if (idx != netif_index) continue;
if (ifa->ifa_addr->sa_family != AF_INET6) continue;
// Skip link-local addresses (fe80::/10)
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if ((sin6->sin6_addr.s6_addr[0] & 0xfe) == 0xfe && sin6->sin6_addr.s6_addr[1] == 0x80) continue;
int is_temporary = (ifa->ifa_flags & IFA_F_TEMPORARY) ? 1 : 0;
if (temporary && is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
}
if (!temporary && !is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
}
}
freeifaddrs(ifaddr);
return found ? 0 : -1;
}
uint32_t get_default_route_netif_index(int family) {
if (family != AF_INET && family != AF_INET6) return 0;
int sock = socket(family, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) return 0;
struct sockaddr_storage bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr));
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&bind_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&bind_addr;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
}
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (bind(sock, (struct sockaddr*)&bind_addr, bind_len) != 0) { close(sock); return 0; }
struct sockaddr_storage remote;
memset(&remote, 0, sizeof(remote));
socklen_t remote_len;
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&remote;
sin->sin_family = AF_INET;
sin->sin_port = htons(53);
inet_pton(AF_INET, "8.8.8.8", &sin->sin_addr);
remote_len = sizeof(struct sockaddr_in);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&remote;
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(53);
inet_pton(AF_INET6, "2001:4860:4860::8888", &sin6->sin6_addr);
remote_len = sizeof(struct sockaddr_in6);
}
if (connect(sock, (struct sockaddr*)&remote, remote_len) != 0) { close(sock); return 0; }
struct sockaddr_storage src;
socklen_t src_len = sizeof(src);
if (getsockname(sock, (struct sockaddr*)&src, &src_len) != 0) { close(sock); return 0; }
close(sock);
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) return 0;
uint32_t result = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_addr->sa_family != family) continue;
if (family == AF_INET) {
struct sockaddr_in* src_sin = (struct sockaddr_in*)&src;
struct sockaddr_in* ifa_sin = (struct sockaddr_in*)ifa->ifa_addr;
if (src_sin->sin_addr.s_addr == ifa_sin->sin_addr.s_addr) {
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
break;
}
} else {
struct sockaddr_in6* src_sin6 = (struct sockaddr_in6*)&src;
struct sockaddr_in6* ifa_sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if (memcmp(&src_sin6->sin6_addr, &ifa_sin6->sin6_addr, 16) == 0) {
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
break;
}
}
}
freeifaddrs(ifaddr);
return result;
}
#else
static uint32_t get_default_route_ip_windows(void) {
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) return 0;
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
(void)netif_index;
return 0;
}
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(53);
remote.sin_addr.s_addr = inet_addr("8.8.8.8");
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
(void)netif_index; (void)temporary;
if (addr_out) memset(addr_out, 0, 16);
return -1;
}
if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
struct sockaddr_in local;
int len = sizeof(local);
if (getsockname(s, (struct sockaddr *)&local, &len) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
closesocket(s);
return local.sin_addr.s_addr;
uint32_t get_default_route_netif_index(int family) {
(void)family;
return 0;
}
#endif
uint32_t get_default_route_ip(void) {
#ifdef _WIN32
return get_default_route_ip_windows();
#else
return get_default_route_ip_linux();
#endif
}
+17 -3
View File
@@ -17,9 +17,13 @@
// Byte order conversion functions
#ifdef _WIN32
#include <stdlib.h>
#define htobe16(x) _byteswap_ushort(x)
#define be16toh(x) _byteswap_ushort(x)
#define htobe32(x) _byteswap_ulong(x)
#define be32toh(x) _byteswap_ulong(x)
#define htobe64(x) _byteswap_uint64(x)
#define be64toh(x) _byteswap_uint64(x)
#elif defined(__linux__)
#else
#include <endian.h>
#endif
@@ -155,9 +159,19 @@
// Returns 0 on success, -1 on error
int random_bytes(uint8_t *buffer, size_t len);
// Get IPv4 address of interface with default route (0.0.0.0 destination)
// Get IPv4 address of interface by its index (ifindex)
// Returns IPv4 address in network byte order, 0 on error
uint32_t get_default_route_ip(void);
uint32_t get_interface_ip_by_index(uint32_t netif_index);
// Get IPv6 address of interface by its index (ifindex)
// temporary=1: get temporary (privacy) IPv6, temporary=0: get permanent IPv6
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out);
// Determine the interface index for the default route (outgoing traffic)
// Creates a temp UDP socket, connects to a well-known address to force
// kernel routing, then matches the chosen source IP to getifaddrs()
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
uint32_t get_default_route_netif_index(int family);
#endif // PLATFORM_COMPAT_H
+906
View File
@@ -0,0 +1,906 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1988, 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Routines to build and maintain radix trees for routing lookups.
*/
#include <string.h>
#include <assert.h>
#include "mem.h"
#include "debug_config.h"
#include "radix.h"
#define min(a, b) ((a) < (b) ? (a) : (b))
static struct radix_node
*rn_insert(void *, struct radix_head *, int *,
struct radix_node [2]),
*rn_newpair(void *, int, struct radix_node[2]),
*rn_search(const void *, struct radix_node *),
*rn_search_m(const void *, struct radix_node *, void *);
static struct radix_node *rn_addmask(const void *, struct radix_mask_head *, int,int);
static void rn_detachhead_internal(struct radix_head *);
#define RADIX_MAX_KEY_LEN 32
static uint8_t rn_zeros[RADIX_MAX_KEY_LEN];
static uint8_t rn_ones[RADIX_MAX_KEY_LEN] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
static int rn_lexobetter(const void *m_arg, const void *n_arg);
static struct radix_mask *
rn_new_radix_mask(struct radix_node *tt,
struct radix_mask *next);
static int rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf,
int skip);
/*
* The data structure for the keys is a radix tree with one way
* branching removed. The index rn_bit at an internal node n represents a bit
* position to be tested. The tree is arranged so that all descendants
* of a node n have keys whose bits all agree up to position rn_bit - 1.
* (We say the index of n is rn_bit.)
*
* There is at least one descendant which has a one bit at position rn_bit,
* and at least one with a zero there.
*
* A route is determined by a pair of key and mask. We require that the
* bit-wise logical and of the key and mask to be the key.
* We define the index of a route to associated with the mask to be
* the first bit number in the mask where 0 occurs (with bit number 0
* representing the highest order bit).
*
* We say a mask is normal if every bit is 0, past the index of the mask.
* If a node n has a descendant (k, m) with index(m) == index(n) == rn_bit,
* and m is a normal mask, then the route applies to every descendant of n.
* If the index(m) < rn_bit, this implies the trailing last few bits of k
* before bit b are all 0, (and hence consequently true of every descendant
* of n), so the route applies to all descendants of the node as well.
*
* Similar logic shows that a non-normal mask m such that
* index(m) <= index(n) could potentially apply to many children of n.
* Thus, for each non-host route, we attach its mask to a list at an internal
* node as high in the tree as we can go.
*
* The present version of the code makes use of normal routes in short-
* circuiting an explict mask and compare operation when testing whether
* a key satisfies a normal route, and also in remembering the unique leaf
* that governs a subtree.
*/
/*
* Most of the functions in this code assume that the key/mask arguments
* are sockaddr-like structures, where the first byte is a uint8_t
* indicating the size of the entire structure.
*
* To make the assumption more explicit, we use the LEN() macro to access
* this field. It is safe to pass an expression with side effects
* to LEN() as the argument is evaluated only once.
*/
#define LEN(x) ( (int) (*(const uint8_t *)(x)) )
static struct radix_node *
rn_search(const void *v_arg, struct radix_node *head)
{
struct radix_node *x;
const uint8_t *v;
for (x = head, v = v_arg; x->rn_bit >= 0;) {
if (x->rn_bmask & v[x->rn_offset])
x = x->rn_right;
else
x = x->rn_left;
}
return (x);
}
static struct radix_node *
rn_search_m(const void *v_arg, struct radix_node *head, void *m_arg)
{
struct radix_node *x;
const uint8_t *v = v_arg, *m = m_arg;
for (x = head; x->rn_bit >= 0;) {
if ((x->rn_bmask & m[x->rn_offset]) &&
(x->rn_bmask & v[x->rn_offset]))
x = x->rn_right;
else
x = x->rn_left;
}
return (x);
}
int
rn_refines(const void *m_arg, const void *n_arg)
{
const uint8_t *m = m_arg, *n = n_arg;
const uint8_t *lim, *lim2 = lim = n + LEN(n);
int longer = LEN(n++) - LEN(m++);
int masks_are_equal = 1;
if (longer > 0)
lim -= longer;
while (n < lim) {
if (*n & ~(*m))
return (0);
if (*n++ != *m++)
masks_are_equal = 0;
}
while (n < lim2)
if (*n++)
return (0);
if (masks_are_equal && (longer < 0))
for (lim2 = m - longer; m < lim2; )
if (*m++)
return (1);
return (!masks_are_equal);
}
struct radix_node *
rn_lookup(const void *v_arg, const void *m_arg, struct radix_head *head)
{
struct radix_node *x;
uint8_t *netmask;
if (m_arg != NULL) {
x = rn_addmask(m_arg, head->rnh_masks, 1,
head->rnh_treetop->rn_offset);
if (x == NULL)
return (NULL);
netmask = x->rn_key;
x = rn_match(v_arg, head);
while (x != NULL && x->rn_mask != netmask)
x = x->rn_dupedkey;
return (x);
}
if ((x = rn_match(v_arg, head)) == NULL)
return (NULL);
if (LEN(x->rn_key) != LEN(v_arg) || memcmp(x->rn_key, v_arg, LEN(v_arg)))
return (NULL);
if (x->rn_mask != NULL)
return (NULL);
return (x);
}
static int
rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf, int skip)
{
const uint8_t *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask;
const uint8_t *cplim;
int length = min(LEN(cp), LEN(cp2));
if (cp3 == NULL)
cp3 = rn_ones;
else
length = min(length, LEN(cp3));
cplim = cp + length; cp3 += skip; cp2 += skip;
for (cp += skip; cp < cplim; cp++, cp2++, cp3++)
if ((*cp ^ *cp2) & *cp3)
return (0);
return (1);
}
struct radix_node *
rn_match(const void *v_arg, struct radix_head *head)
{
const uint8_t *v = v_arg;
struct radix_node *t = head->rnh_treetop, *x;
const uint8_t *cp = v, *cp2;
const uint8_t *cplim;
struct radix_node *saved_t, *top = t;
int off = t->rn_offset, vlen = LEN(cp), matched_off;
int test, b, rn_bit;
for (; t->rn_bit >= 0; ) {
if (t->rn_bmask & cp[t->rn_offset])
t = t->rn_right;
else
t = t->rn_left;
}
if (t->rn_mask)
vlen = *t->rn_mask;
cp += off; cp2 = t->rn_key + off; cplim = v + vlen;
for (; cp < cplim; cp++, cp2++)
if (*cp != *cp2)
goto on1;
if (t->rn_flags & RNF_ROOT)
t = t->rn_dupedkey;
return (t);
on1:
test = (*cp ^ *cp2) & 0xff;
for (b = 7; (test >>= 1) > 0;)
b--;
matched_off = cp - v;
b += matched_off << 3;
rn_bit = -1 - b;
if ((saved_t = t)->rn_mask == 0)
t = t->rn_dupedkey;
for (; t; t = t->rn_dupedkey)
if (t->rn_flags & RNF_NORMAL) {
if (rn_bit <= t->rn_bit)
return (t);
} else if (rn_satisfies_leaf(v, t, matched_off))
return (t);
t = saved_t;
do {
struct radix_mask *m;
t = t->rn_parent;
m = t->rn_mklist;
while (m) {
if (m->rm_flags & RNF_NORMAL) {
if (rn_bit <= m->rm_bit)
return (m->rm_leaf);
} else {
off = min(t->rn_offset, matched_off);
x = rn_search_m(v, t, m->rm_mask);
while (x && x->rn_mask != m->rm_mask)
x = x->rn_dupedkey;
if (x && rn_satisfies_leaf(v, x, off))
return (x);
}
m = m->rm_mklist;
}
} while (t != top);
return (0);
}
struct radix_node *
rn_nextprefix(struct radix_node *rn)
{
for (rn = rn->rn_dupedkey; rn != NULL; rn = rn->rn_dupedkey) {
if (!(rn->rn_flags & RNF_ROOT))
return (rn);
}
return (NULL);
}
static struct radix_node *
rn_newpair(void *v, int b, struct radix_node nodes[2])
{
struct radix_node *tt = nodes, *t = tt + 1;
t->rn_bit = b;
t->rn_bmask = 0x80 >> (b & 7);
t->rn_left = tt;
t->rn_offset = b >> 3;
t->rn_parent = t->rn_right = NULL;
tt->rn_bit = -1;
tt->rn_mask = NULL;
tt->rn_dupedkey = NULL;
tt->rn_bmask = 0;
tt->rn_key = (uint8_t *)v;
tt->rn_parent = t;
tt->rn_flags = t->rn_flags = RNF_ACTIVE;
tt->rn_mklist = t->rn_mklist = 0;
return (t);
}
static struct radix_node *
rn_insert(void *v_arg, struct radix_head *head, int *dupentry,
struct radix_node nodes[2])
{
uint8_t *v = v_arg;
struct radix_node *top = head->rnh_treetop;
int head_off = top->rn_offset, vlen = LEN(v);
struct radix_node *t = rn_search(v_arg, top);
uint8_t *cp = v + head_off;
unsigned b;
struct radix_node *p, *tt, *x;
uint8_t *cp2 = t->rn_key + head_off;
int cmp_res;
uint8_t *cplim = v + vlen;
while (cp < cplim)
if (*cp2++ != *cp++)
goto on1;
*dupentry = 1;
return (t);
on1:
*dupentry = 0;
cmp_res = (cp[-1] ^ cp2[-1]) & 0xff;
for (b = (cp - v) << 3; cmp_res; b--)
cmp_res >>= 1;
x = top;
cp = v;
do {
p = x;
if (cp[x->rn_offset] & x->rn_bmask)
x = x->rn_right;
else
x = x->rn_left;
} while (b > (unsigned) x->rn_bit);
t = rn_newpair(v_arg, b, nodes);
tt = t->rn_left;
if ((cp[p->rn_offset] & p->rn_bmask) == 0)
p->rn_left = t;
else
p->rn_right = t;
x->rn_parent = t;
t->rn_parent = p;
if ((cp[t->rn_offset] & t->rn_bmask) == 0) {
t->rn_right = x;
} else {
t->rn_right = tt;
t->rn_left = x;
}
return (tt);
}
static struct radix_node *
rn_addmask(const void *n_arg, struct radix_mask_head *maskhead, int search, int skip)
{
const uint8_t *netmask = n_arg;
const uint8_t *c, *clim;
uint8_t *cp;
struct radix_node *x;
int b = 0, mlen, j;
int maskduplicated, isnormal;
struct radix_node *saved_x;
uint8_t addmask_key[RADIX_MAX_KEY_LEN];
if ((mlen = LEN(netmask)) > RADIX_MAX_KEY_LEN)
mlen = RADIX_MAX_KEY_LEN;
if (skip == 0)
skip = 1;
if (mlen <= skip)
return (maskhead->mask_nodes);
memset(addmask_key, 0, RADIX_MAX_KEY_LEN);
if (skip > 1)
memcpy(addmask_key + 1, rn_ones + 1, skip - 1);
memcpy(addmask_key + skip, netmask + skip, mlen - skip);
for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;)
cp--;
mlen = cp - addmask_key;
if (mlen <= skip)
return (maskhead->mask_nodes);
*addmask_key = mlen;
x = rn_search(addmask_key, maskhead->head.rnh_treetop);
if (memcmp(addmask_key, x->rn_key, mlen) != 0)
x = NULL;
if (x || search)
return (x);
R_Zalloc(x, struct radix_node *, RADIX_MAX_KEY_LEN + 2 * sizeof (*x));
if ((saved_x = x) == NULL)
return (0);
netmask = cp = (uint8_t *)(x + 2);
memcpy(cp, addmask_key, mlen);
x = rn_insert(cp, &maskhead->head, &maskduplicated, x);
if (maskduplicated) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_addmask: mask impossibly already in tree");
R_Free(saved_x);
return (x);
}
#define CONTIG(_c) (((~(_c) + 1) & (_c)) == (uint8_t)(~(_c) + 1))
clim = netmask + mlen;
isnormal = 1;
for (c = netmask + skip; c < clim && *c == 0xff; c++);
if (c != clim) {
for (j = 0x80; (j & *c) != 0; j >>= 1)
b++;
if (!CONTIG(*c) || c != (clim - 1))
isnormal = 0;
}
b += (c - netmask) << 3;
x->rn_bit = -1 - b;
if (isnormal)
x->rn_flags |= RNF_NORMAL;
return (x);
}
static int
rn_lexobetter(const void *m_arg, const void *n_arg)
{
const uint8_t *mp = m_arg, *np = n_arg, *lim;
if (LEN(mp) > LEN(np))
return (1);
if (LEN(mp) == LEN(np))
for (lim = mp + LEN(mp); mp < lim;)
if (*mp++ > *np++)
return (1);
return (0);
}
static struct radix_mask *
rn_new_radix_mask(struct radix_node *tt, struct radix_mask *next)
{
struct radix_mask *m;
R_Malloc(m, struct radix_mask *, sizeof (struct radix_mask));
if (m == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to allocate route mask");
return (0);
}
memset(m, 0, sizeof(*m));
m->rm_bit = tt->rn_bit;
m->rm_flags = tt->rn_flags;
if (tt->rn_flags & RNF_NORMAL)
m->rm_leaf = tt;
else
m->rm_mask = tt->rn_mask;
m->rm_mklist = next;
tt->rn_mklist = m;
return (m);
}
struct radix_node *
rn_addroute(void *v_arg, const void *n_arg, struct radix_head *head,
struct radix_node treenodes[2])
{
uint8_t *v = (uint8_t *)v_arg, *netmask = NULL;
struct radix_node *t, *x = NULL, *tt;
struct radix_node *saved_tt, *top = head->rnh_treetop;
short b = 0, b_leaf = 0;
int keyduplicated;
uint8_t *mmask;
struct radix_mask *m, **mp;
if (n_arg) {
x = rn_addmask(n_arg, head->rnh_masks, 0, top->rn_offset);
if (x == NULL)
return (0);
b_leaf = x->rn_bit;
b = -1 - x->rn_bit;
netmask = x->rn_key;
}
saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes);
if (keyduplicated) {
for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) {
if (tt->rn_mask == netmask)
return (0);
if (netmask == 0 ||
(tt->rn_mask &&
((b_leaf < tt->rn_bit)
|| rn_refines(netmask, tt->rn_mask)
|| rn_lexobetter(netmask, tt->rn_mask))))
break;
}
if (tt == saved_tt) {
struct radix_node *xx = x;
(tt = treenodes)->rn_dupedkey = t;
tt->rn_flags = t->rn_flags;
tt->rn_parent = x = t->rn_parent;
t->rn_parent = tt;
if (x->rn_left == t)
x->rn_left = tt;
else
x->rn_right = tt;
saved_tt = tt; x = xx;
} else {
(tt = treenodes)->rn_dupedkey = t->rn_dupedkey;
t->rn_dupedkey = tt;
tt->rn_parent = t;
if (tt->rn_dupedkey)
tt->rn_dupedkey->rn_parent = tt;
}
tt->rn_key = (uint8_t *) v;
tt->rn_bit = -1;
tt->rn_flags = RNF_ACTIVE;
}
if (netmask) {
tt->rn_mask = netmask;
tt->rn_bit = x->rn_bit;
tt->rn_flags |= x->rn_flags & RNF_NORMAL;
}
t = saved_tt->rn_parent;
if (keyduplicated)
goto on2;
b_leaf = -1 - t->rn_bit;
if (t->rn_right == saved_tt)
x = t->rn_left;
else
x = t->rn_right;
if (x->rn_bit < 0) {
for (mp = &t->rn_mklist; x; x = x->rn_dupedkey)
if (x->rn_mask && (x->rn_bit >= b_leaf) && x->rn_mklist == 0) {
*mp = m = rn_new_radix_mask(x, 0);
if (m)
mp = &m->rm_mklist;
}
} else if (x->rn_mklist) {
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
if (m->rm_bit >= b_leaf)
break;
t->rn_mklist = m; *mp = NULL;
}
on2:
if ((netmask == 0) || (b > t->rn_bit ))
return (tt);
b_leaf = tt->rn_bit;
do {
x = t;
t = t->rn_parent;
} while (b <= t->rn_bit && x != top);
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) {
if (m->rm_bit < b_leaf)
continue;
if (m->rm_bit > b_leaf)
break;
if (m->rm_flags & RNF_NORMAL) {
mmask = m->rm_leaf->rn_mask;
if (tt->rn_flags & RNF_NORMAL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
"Non-unique normal route, mask not entered");
return (tt);
}
} else
mmask = m->rm_mask;
if (mmask == netmask) {
m->rm_refs++;
tt->rn_mklist = m;
return (tt);
}
if (rn_refines(netmask, mmask)
|| rn_lexobetter(netmask, mmask))
break;
}
*mp = rn_new_radix_mask(tt, *mp);
return (tt);
}
struct radix_node *
rn_delete(const void *v_arg, const void *netmask_arg, struct radix_head *head)
{
struct radix_node *t, *p, *x, *tt;
struct radix_mask *m, *saved_m, **mp;
struct radix_node *dupedkey, *saved_tt, *top;
const uint8_t *v;
const uint8_t *netmask;
int b, head_off, vlen;
v = v_arg;
netmask = netmask_arg;
x = head->rnh_treetop;
tt = rn_search(v, x);
head_off = x->rn_offset;
vlen = LEN(v);
saved_tt = tt;
top = x;
if (tt == NULL ||
memcmp(v + head_off, tt->rn_key + head_off, vlen - head_off))
return (0);
if (netmask) {
x = rn_addmask(netmask, head->rnh_masks, 1, head_off);
if (x == NULL)
return (0);
netmask = x->rn_key;
while (tt->rn_mask != netmask)
if ((tt = tt->rn_dupedkey) == NULL)
return (0);
}
if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == NULL)
goto on1;
if (tt->rn_flags & RNF_NORMAL) {
if (m->rm_leaf != tt || m->rm_refs > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
return (0);
}
} else {
if (m->rm_mask != tt->rn_mask) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
goto on1;
}
if (--m->rm_refs >= 0)
goto on1;
}
b = -1 - tt->rn_bit;
t = saved_tt->rn_parent;
if (b > t->rn_bit)
goto on1;
do {
x = t;
t = t->rn_parent;
} while (b <= t->rn_bit && x != top);
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
if (m == saved_m) {
*mp = m->rm_mklist;
R_Free(m);
break;
}
if (m == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find our annotation");
if (tt->rn_flags & RNF_NORMAL)
return (0);
}
on1:
if (tt->rn_flags & RNF_ROOT)
return (0);
t = tt->rn_parent;
dupedkey = saved_tt->rn_dupedkey;
if (dupedkey) {
if (tt == saved_tt) {
x = dupedkey; x->rn_parent = t;
if (t->rn_left == tt)
t->rn_left = x;
else
t->rn_right = x;
} else {
for (x = p = saved_tt; p && p->rn_dupedkey != tt;)
p = p->rn_dupedkey;
if (p) {
p->rn_dupedkey = tt->rn_dupedkey;
if (tt->rn_dupedkey)
tt->rn_dupedkey->rn_parent = p;
} else DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find us");
}
t = tt + 1;
if (t->rn_flags & RNF_ACTIVE) {
*++x = *t;
p = t->rn_parent;
if (p->rn_left == t)
p->rn_left = x;
else
p->rn_right = x;
x->rn_left->rn_parent = x;
x->rn_right->rn_parent = x;
}
goto out;
}
if (t->rn_left == tt)
x = t->rn_right;
else
x = t->rn_left;
p = t->rn_parent;
if (p->rn_right == t)
p->rn_right = x;
else
p->rn_left = x;
x->rn_parent = p;
if (t->rn_mklist) {
if (x->rn_bit >= 0) {
for (mp = &x->rn_mklist; (m = *mp);)
mp = &m->rm_mklist;
*mp = t->rn_mklist;
} else {
for (m = t->rn_mklist; m && x; x = x->rn_dupedkey)
if (m == x->rn_mklist) {
struct radix_mask *mm = m->rm_mklist;
x->rn_mklist = 0;
if (--(m->rm_refs) < 0)
R_Free(m);
m = mm;
}
if (m)
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
"rn_delete: Orphaned Mask %p at %p", m, x);
}
}
x = tt + 1;
if (t != x) {
*t = *x;
t->rn_left->rn_parent = t;
t->rn_right->rn_parent = t;
p = x->rn_parent;
if (p->rn_left == x)
p->rn_left = t;
else
p->rn_right = t;
}
out:
tt->rn_flags &= ~RNF_ACTIVE;
tt[1].rn_flags &= ~RNF_ACTIVE;
return (tt);
}
int
rn_walktree_from(struct radix_head *h, void *a, void *m,
walktree_f_t *f, void *w)
{
int error;
struct radix_node *base, *next;
uint8_t *xa = (uint8_t *)a;
uint8_t *xm = (uint8_t *)m;
struct radix_node *rn, *last = NULL;
int stopping = 0;
int lastb;
assert(m != NULL);
for (rn = h->rnh_treetop; rn->rn_bit >= 0; ) {
last = rn;
if (!(rn->rn_bmask & xm[rn->rn_offset]))
break;
if (rn->rn_bmask & xa[rn->rn_offset])
rn = rn->rn_right;
else
rn = rn->rn_left;
}
if (rn->rn_bit >= 0)
rn = last;
lastb = last->rn_bit;
while (rn->rn_bit >= 0)
rn = rn->rn_left;
while (!stopping) {
base = rn;
while (rn->rn_parent->rn_right == rn
&& !(rn->rn_flags & RNF_ROOT)) {
rn = rn->rn_parent;
if (rn->rn_bit <= lastb)
stopping = 1;
}
if (rn->rn_parent->rn_flags & RNF_ROOT)
stopping = 1;
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
rn = rn->rn_left;
next = rn;
while ((rn = base) != NULL) {
base = rn->rn_dupedkey;
if (!(rn->rn_flags & RNF_ROOT)
&& (error = (*f)(rn, w)))
return (error);
}
rn = next;
if (rn->rn_flags & RNF_ROOT)
stopping = 1;
}
return (0);
}
int
rn_walktree(struct radix_head *h, walktree_f_t *f, void *w)
{
int error;
struct radix_node *base, *next;
struct radix_node *rn = h->rnh_treetop;
while (rn->rn_bit >= 0)
rn = rn->rn_left;
for (;;) {
base = rn;
while (rn->rn_parent->rn_right == rn
&& (rn->rn_flags & RNF_ROOT) == 0)
rn = rn->rn_parent;
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
rn = rn->rn_left;
next = rn;
while ((rn = base)) {
base = rn->rn_dupedkey;
if (!(rn->rn_flags & RNF_ROOT)
&& (error = (*f)(rn, w)))
return (error);
}
rn = next;
if (rn->rn_flags & RNF_ROOT)
return (0);
}
}
void
rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes, int off)
{
struct radix_node *t, *tt, *ttt;
t = rn_newpair(rn_zeros, off, base_nodes);
ttt = base_nodes + 2;
t->rn_right = ttt;
t->rn_parent = t;
tt = t->rn_left;
tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE;
tt->rn_bit = -1 - off;
*ttt = *tt;
ttt->rn_key = rn_ones;
rh->rnh_treetop = t;
}
static void
rn_detachhead_internal(struct radix_head *head)
{
assert(head != NULL);
R_Free(head);
}
int
rn_inithead(void **head, int off)
{
struct radix_node_head *rnh;
struct radix_mask_head *rmh;
rnh = *head;
rmh = NULL;
if (*head != NULL)
return (1);
R_Zalloc(rnh, struct radix_node_head *, sizeof (*rnh));
R_Zalloc(rmh, struct radix_mask_head *, sizeof (*rmh));
if (rnh == NULL || rmh == NULL) {
if (rnh != NULL)
R_Free(rnh);
if (rmh != NULL)
R_Free(rmh);
return (0);
}
rn_inithead_internal(&rnh->rh, rnh->rnh_nodes, off);
rn_inithead_internal(&rmh->head, rmh->mask_nodes, 0);
*head = rnh;
rnh->rh.rnh_masks = rmh;
rnh->rnh_addaddr = rn_addroute;
rnh->rnh_deladdr = rn_delete;
rnh->rnh_matchaddr = rn_match;
rnh->rnh_lookup = rn_lookup;
rnh->rnh_walktree = rn_walktree;
rnh->rnh_walktree_from = rn_walktree_from;
return (1);
}
static int
rn_freeentry(struct radix_node *rn, void *arg)
{
struct radix_head * const rnh = arg;
struct radix_node *x;
x = (struct radix_node *)rn_delete(rn + 2, NULL, rnh);
if (x != NULL)
R_Free(x);
return (0);
}
int
rn_detachhead(void **head)
{
struct radix_node_head *rnh;
assert(head != NULL && *head != NULL);
rnh = (struct radix_node_head *)(*head);
rn_walktree(&rnh->rh.rnh_masks->head, rn_freeentry, rnh->rh.rnh_masks);
rn_detachhead_internal(&rnh->rh.rnh_masks->head);
rn_detachhead_internal(&rnh->rh);
*head = NULL;
return (1);
}
+164
View File
@@ -0,0 +1,164 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1988, 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _RADIX_H_
#define _RADIX_H_
#include <stdint.h>
#include "mem.h"
/*
* Radix search tree node layout.
*/
struct radix_node {
struct radix_mask *rn_mklist; /* list of masks contained in subtree */
struct radix_node *rn_parent; /* parent */
short rn_bit; /* bit offset; -1-index(netmask) */
char rn_bmask; /* node: mask for bit test*/
uint8_t rn_flags; /* enumerated next */
#define RNF_NORMAL 1 /* leaf contains normal route */
#define RNF_ROOT 2 /* leaf is root leaf for tree */
#define RNF_ACTIVE 4 /* This node is alive (for rtfree) */
union {
struct { /* leaf only data: */
uint8_t *rn_Key; /* object of search */
uint8_t *rn_Mask; /* netmask, if present */
struct radix_node *rn_Dupedkey;
} rn_leaf;
struct { /* node only data: */
int rn_Off; /* where to start compare */
struct radix_node *rn_L;/* progeny */
struct radix_node *rn_R;/* progeny */
} rn_node;
} rn_u;
};
#define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey
#define rn_key rn_u.rn_leaf.rn_Key
#define rn_mask rn_u.rn_leaf.rn_Mask
#define rn_offset rn_u.rn_node.rn_Off
#define rn_left rn_u.rn_node.rn_L
#define rn_right rn_u.rn_node.rn_R
/*
* Annotations to tree concerning potential routes applying to subtrees.
*/
struct radix_mask {
short rm_bit; /* bit offset; -1-index(netmask) */
char rm_unused; /* cf. rn_bmask */
uint8_t rm_flags; /* cf. rn_flags */
struct radix_mask *rm_mklist; /* more masks to try */
union {
uint8_t *rmu_mask; /* the mask */
struct radix_node *rmu_leaf; /* for normal routes */
} rm_rmu;
int rm_refs; /* # of references to this struct */
};
#define rm_mask rm_rmu.rmu_mask
#define rm_leaf rm_rmu.rmu_leaf /* extra field would make 32 bytes */
struct radix_head;
typedef int walktree_f_t(struct radix_node *, void *);
typedef struct radix_node *rn_matchaddr_f_t(const void *v,
struct radix_head *head);
typedef struct radix_node *rn_addaddr_f_t(void *v, const void *mask,
struct radix_head *head, struct radix_node nodes[]);
typedef struct radix_node *rn_deladdr_f_t(const void *v, const void *mask,
struct radix_head *head);
typedef struct radix_node *rn_lookup_f_t(const void *v, const void *mask,
struct radix_head *head);
typedef int rn_walktree_t(struct radix_head *head, walktree_f_t *f,
void *w);
typedef int rn_walktree_from_t(struct radix_head *head,
void *a, void *m, walktree_f_t *f, void *w);
typedef void rn_close_t(struct radix_node *rn, struct radix_head *head);
struct radix_node *rn_nextprefix(struct radix_node *rn);
struct radix_mask_head;
struct radix_head {
struct radix_node *rnh_treetop;
struct radix_mask_head *rnh_masks; /* Storage for our masks */
};
struct radix_node_head {
struct radix_head rh;
rn_matchaddr_f_t *rnh_matchaddr; /* longest match for sockaddr */
rn_addaddr_f_t *rnh_addaddr; /* add based on sockaddr*/
rn_deladdr_f_t *rnh_deladdr; /* remove based on sockaddr */
rn_lookup_f_t *rnh_lookup; /* exact match for sockaddr */
rn_walktree_t *rnh_walktree; /* traverse tree */
rn_walktree_from_t *rnh_walktree_from; /* traverse tree below a */
rn_close_t *rnh_close; /*do something when the last ref drops*/
struct radix_node rnh_nodes[3]; /* empty tree for common case */
};
struct radix_mask_head {
struct radix_head head;
struct radix_node mask_nodes[3];
};
void rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes,
int off);
#define R_Malloc(p, t, n) (p = (t) u_malloc((unsigned int)(n)))
#define R_Zalloc(p, t, n) (p = (t) u_calloc(1, (unsigned int)(n)))
#define R_Free(p) u_free(p)
/* Stub lock macros — single-threaded with u_async */
#define RADIX_NODE_HEAD_RLOCK_TRACKER
#define RADIX_NODE_HEAD_LOCK_INIT(rnh)
#define RADIX_NODE_HEAD_LOCK(rnh)
#define RADIX_NODE_HEAD_UNLOCK(rnh)
#define RADIX_NODE_HEAD_RLOCK(rnh)
#define RADIX_NODE_HEAD_RUNLOCK(rnh)
#define RADIX_NODE_HEAD_DESTROY(rnh)
#define RADIX_NODE_HEAD_LOCK_ASSERT(rnh)
#define RADIX_NODE_HEAD_WLOCK_ASSERT(rnh)
int rn_inithead(void **, int);
int rn_detachhead(void **);
int rn_refines(const void *, const void *);
struct radix_node *rn_addroute(void *, const void *, struct radix_head *,
struct radix_node[2]);
struct radix_node *rn_delete(const void *, const void *, struct radix_head *);
struct radix_node *rn_lookup (const void *v_arg, const void *m_arg,
struct radix_head *head);
struct radix_node *rn_match(const void *, struct radix_head *);
int rn_walktree_from(struct radix_head *h, void *a, void *m,
walktree_f_t *f, void *w);
int rn_walktree(struct radix_head *, walktree_f_t *, void *);
#endif /* _RADIX_H_ */
+348
View File
@@ -0,0 +1,348 @@
// serialize.c - Universal binary serialization with dynamic allocation
#include "serialize.h"
#include <string.h>
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size схемы
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
static uint16_t length_encode(uint8_t* out, uint16_t val) {
if (out) {
out[0] = (uint8_t)(val >> 8);
out[1] = (uint8_t)val;
}
return 2;
}
static uint16_t length_decode(const uint8_t* in, size_t in_len, size_t* consumed) {
if (!in || in_len < 2) {
if (consumed) *consumed = 0;
return 0;
}
if (consumed) *consumed = 2;
return ((uint16_t)in[0] << 8) | in[1];
}
static uint32_t get_var_count(const uint8_t* data, const struct SerializeField* f) {
if (!data || !f) return 0;
const uint8_t* len_ptr = data + f->len_offset;
if (f->type == SERIALIZE_TYPE_ARRAY_U8) return *len_ptr;
if (f->type == SERIALIZE_TYPE_ARRAY_U16) return *(const uint16_t*)len_ptr;
if (f->type == SERIALIZE_TYPE_ARRAY_U32) return *(const uint32_t*)len_ptr;
return 0;
}
static void set_var_count(uint8_t* data, const struct SerializeField* f, uint16_t count) {
if (!data || !f) return;
uint8_t* len_ptr = data + f->len_offset;
if (f->type == SERIALIZE_TYPE_ARRAY_U8) *len_ptr = (uint8_t)count;
else if (f->type == SERIALIZE_TYPE_ARRAY_U16) *(uint16_t*)len_ptr = count;
else if (f->type == SERIALIZE_TYPE_ARRAY_U32) *(uint32_t*)len_ptr = count;
}
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len) {
if (!schema || !structure || !out_buf || !out_len) return SERIALIZE_ERR_NULL;
const uint8_t* data = (const uint8_t*)structure;
size_t total = schema->header_len;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_FIXED || f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
total += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint32_t count = 0;
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
const char* str = *(const char**)(data + f->offset);
count = str ? (uint32_t)strlen(str) : 0;
} else {
count = get_var_count(data, f);
}
uint16_t len = (count > 0xFFFFu) ? 0xFFFFu : (uint16_t)count;
total += 2;
if (f->elem_size == 1) {
total += (size_t)len + 1; // данные + '\0'
} else {
total += (size_t)len * f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
// подсчёт узлов (максимум 65535)
void* cur = *(void**)(data + f->offset);
uint16_t count = 0;
while (cur && count < 0xFFFFu) {
count++;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
total += 2 + (size_t)count * f->elem_size;
} else {
return SERIALIZE_ERR_NOTSUP;
}
}
if (schema->max_size > 0 && total > schema->max_size) {
return SERIALIZE_ERR_SIZE;
}
uint8_t* buf = u_malloc(total);
if (!buf) return SERIALIZE_ERR_BUF;
uint8_t* ptr = buf;
if (header && schema->header_len > 0) {
memcpy(ptr, header, schema->header_len);
ptr += schema->header_len;
}
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
const uint8_t* fld = data + f->offset;
if (f->type == SERIALIZE_TYPE_FIXED) {
if (f->elem_size > 0) {
memcpy(ptr, fld, f->elem_size);
ptr += f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
const uint8_t* arr = *(const uint8_t**)fld;
if (f->elem_size > 0) {
if (arr) memcpy(ptr, arr, f->elem_size);
else memset(ptr, 0, f->elem_size);
}
ptr += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint32_t raw_count = 0;
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
const char* str = *(const char**)fld;
raw_count = str ? (uint32_t)strlen(str) : 0;
} else {
raw_count = get_var_count(data, f);
}
uint16_t len = (raw_count > 0xFFFFu) ? 0xFFFFu : (uint16_t)raw_count;
if (len > 0 && f->type == SERIALIZE_TYPE_ASCIIZ) {
if (!*(const char**)fld) len = 0;
} else if (len > 0 && f->type != SERIALIZE_TYPE_ASCIIZ) {
if (!*(const uint8_t**)fld) len = 0;
}
ptr += length_encode(ptr, len);
if (f->elem_size == 1) { // ASCIIZ
if (len > 0) {
const char* str = *(const char**)fld;
if (str) memcpy(ptr, str, len);
ptr += len;
}
*ptr++ = '\0';
} else { // array
if (len > 0) {
const uint8_t* arr = *(const uint8_t**)fld;
if (arr) memcpy(ptr, arr, (size_t)len * f->elem_size);
ptr += (size_t)len * f->elem_size;
}
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
void* cur = *(void**)(data + f->offset);
uint16_t count = 0;
while (cur && count < 0xFFFFu) {
count++;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
ptr += length_encode(ptr, count);
cur = *(void**)(data + f->offset);
for (uint16_t i = 0; i < count; i++) {
if (!cur) break;
memcpy(ptr, cur, f->elem_size);
ptr += f->elem_size;
cur = *(void**)((uint8_t*)cur + f->len_offset);
}
}
}
*out_buf = buf;
*out_len = ptr - buf;
return SERIALIZE_ERR_OK;
}
int serialize_decode(const uint8_t* in_buf,
size_t in_len,
const struct SerializeSchema* schema,
void** structure) {
if (!in_buf || !schema || !structure) {
if (structure) *structure = NULL;
return SERIALIZE_ERR_NULL;
}
void* obj = u_malloc(schema->struct_size);
if (!obj) {
*structure = NULL;
return SERIALIZE_ERR_BUF;
}
memset(obj, 0, schema->struct_size);
const uint8_t* ptr = in_buf;
const uint8_t* end = in_buf + in_len;
uint8_t* data = (uint8_t*)obj;
size_t consumed;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_FIXED) {
if (ptr + f->elem_size > end) goto decode_error;
if (f->elem_size > 0) {
memcpy(data + f->offset, ptr, f->elem_size);
ptr += f->elem_size;
}
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
if (ptr + f->elem_size > end) goto decode_error;
uint8_t* arr = NULL;
if (f->elem_size > 0) {
arr = u_malloc(f->elem_size);
if (arr) memcpy(arr, ptr, f->elem_size);
}
*(uint8_t**)(data + f->offset) = arr;
if (f->elem_size > 0 && !arr) goto decode_error;
ptr += f->elem_size;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
uint16_t len = length_decode(ptr, end - ptr, &consumed);
if (consumed == 0) goto decode_error;
ptr += consumed;
int is_str = (f->elem_size == 1);
size_t needed = is_str ? (size_t)len + 1 : (size_t)len * f->elem_size;
if ((size_t)(end - ptr) < needed) goto decode_error;
if (is_str) {
char* str = NULL;
if (len == 0) {
str = u_malloc(1);
if (str) str[0] = '\0';
} else {
str = u_malloc(len + 1);
if (str) {
memcpy(str, ptr, len);
str[len] = '\0';
}
}
*(char**)(data + f->offset) = str;
if (!str) goto decode_error;
ptr += needed;
} else {
uint8_t* arr = NULL;
if (needed > 0) {
arr = u_malloc(needed);
if (arr) memcpy(arr, ptr, needed);
}
*(uint8_t**)(data + f->offset) = arr;
if (needed > 0 && !arr) goto decode_error;
ptr += needed;
}
if (f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32) {
set_var_count(data, f, len);
}
} else if (f->type == SERIALIZE_TYPE_LINKED) {
uint16_t len = length_decode(ptr, end - ptr, &consumed);
if (consumed == 0) goto decode_error;
ptr += consumed;
void* head = NULL;
void* prev = NULL;
for (uint16_t i = 0; i < len; i++) {
if (ptr + f->elem_size > end) {
// освободить уже созданную часть списка
void* n = head;
while (n) {
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
u_free(n);
n = nxt;
}
goto decode_error;
}
void* new_node = u_malloc(f->elem_size);
if (!new_node) {
void* n = head;
while (n) {
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
u_free(n);
n = nxt;
}
goto decode_error;
}
memcpy(new_node, ptr, f->elem_size);
// обязательно обнуляем next в новом узле
*(void**)((uint8_t*)new_node + f->len_offset) = NULL;
if (!head) {
head = new_node;
} else {
*(void**)((uint8_t*)prev + f->len_offset) = new_node;
}
prev = new_node;
ptr += f->elem_size;
}
*(void**)(data + f->offset) = head;
} else {
goto decode_error;
}
}
*structure = obj;
return SERIALIZE_ERR_OK;
decode_error:
serialize_free(schema, &obj);
*structure = NULL;
return SERIALIZE_ERR_BUF;
}
void serialize_free(const struct SerializeSchema* schema, void** structure) {
if (!schema || !structure || !*structure) return;
uint8_t* data = (uint8_t*)*structure;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->type == SERIALIZE_TYPE_LINKED) {
void** headp = (void**)(data + f->offset);
void* node = *headp;
while (node) {
void* next = *(void**)((uint8_t*)node + f->len_offset);
u_free(node);
node = next;
}
*headp = NULL;
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
f->type == SERIALIZE_TYPE_ARRAY_FIXED ||
(f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32)) {
uint8_t** p = (uint8_t**)(data + f->offset);
if (*p) {
u_free(*p);
*p = NULL;
}
}
}
u_free(*structure);
*structure = NULL;
}
+130
View File
@@ -0,0 +1,130 @@
/**
* @file serialize.h
* @brief Бинарная сериализация структур с динамическим выделением памяти
*
* Библиотека преобразует C-структуру, содержащую:
* - фиксированные поля,
* - ASCIIZ-строки (char*),
* - динамические массивы (uint8_t* / char*),
* - singly-linked списки
* в компактный бинарный буфер и обратно.
*
* Поддерживаемые типы полей (см. SERIALIZE_TYPE_*):
* 0 — фиксированные данные (встроены в структуру)
* 1 — ASCIIZ-строка (char*, длина = strlen)
* 2 — массив ровно из 1 элемента (указатель)
* 3 — массив, количество элементов в UINT8 (по len_offset)
* 4 — массив, количество элементов в UINT16 (по len_offset)
* 5 — массив, количество элементов в UINT32 (по len_offset)
* 6 — singly-linked list (next-указатель по смещению len_offset)
*
* Особенности:
* • Все динамические данные (строки, массивы, списки) выделяются через u_malloc.
* • При encode длина переменных полей кодируется всегда 2 байтами (uint16_t, big-endian).
* • Для linked list в буфере сохраняется только количество узлов + сырые данные узлов
* (next-указатели НЕ сериализуются, они восстанавливаются при decode).
* • serialize_decode принимает буфер БЕЗ заголовка.
* • serialize_free освобождает ВСЮ структуру и все вложенные динамические объекты.
* • Поля в schema.fields должны идти в порядке возрастания offset.
* • max_size (если > 0) — жёсткий лимит размера выходного буфера.
*
* @example
* // 1. Описание структуры
* typedef struct Node {
* uint32_t value;
* struct Node* next; // next по offset = 4
* } Node;
*
* typedef struct {
* uint8_t id;
* char* name;
* uint8_t name_len;
* uint8_t* data;
* uint16_t data_cnt;
* Node* list; // linked list
* } MyStruct;
*
* // 2. Схема
* static const struct SerializeField my_fields[] = {
* {offsetof(MyStruct, id), 1, SERIALIZE_TYPE_FIXED, 0},
* {offsetof(MyStruct, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(MyStruct, name_len)},
* {offsetof(MyStruct, data), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(MyStruct, data_cnt)},
* {offsetof(MyStruct, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)}
* };
*
* static const struct SerializeSchema my_schema = {
* .field_count = 4,
* .struct_size = sizeof(MyStruct),
* .max_size = 4096, // опционально
* .header_len = 4,
* .fields = my_fields
* };
*
* // 3. Использование
* uint8_t header[] = {0x01, 0x02, 0x03, 0x04};
* uint8_t *buf = NULL;
* size_t len;
*
* serialize_encode(&orig, &my_schema, header, &buf, &len);
*
* MyStruct *restored = NULL;
* uint8_t *data = buf + my_schema.header_len;
* serialize_decode(data, len - my_schema.header_len, &my_schema, (void**)&restored);
*
* serialize_free(&my_schema, (void**)&restored);
* u_free(buf);
*/
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/mem.h"
#define SERIALIZE_TYPE_FIXED 0 ///< фиксированные данные, elem_size = размер
#define SERIALIZE_TYPE_ASCIIZ 1 ///< char* (ASCIIZ, длина = strlen)
#define SERIALIZE_TYPE_ARRAY_FIXED 2 ///< *array ровно 1 элемент
#define SERIALIZE_TYPE_ARRAY_U8 3 ///< *array, счётчик UINT8
#define SERIALIZE_TYPE_ARRAY_U16 4 ///< *array, счётчик UINT16
#define SERIALIZE_TYPE_ARRAY_U32 5 ///< *array, счётчик UINT32
#define SERIALIZE_TYPE_LINKED 6 ///< linked_list (next по смещению len_offset)
/**
* @brief Описание одного поля структуры для сериализации
*/
struct SerializeField {
uint16_t offset; ///< смещение поля в структуре
uint8_t elem_size; ///< размер элемента (для fixed = размер данных, для array/linked = размер одного элемента)
uint8_t type; ///< SERIALIZE_TYPE_*
uint16_t len_offset; ///< смещение поля-счётчика (игнорируется для type 0,1,2)
};
/**
* @brief Схема структуры для сериализации
*/
struct SerializeSchema {
uint16_t field_count; ///< число полей
uint16_t struct_size; ///< размер основной структуры
uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения)
uint16_t header_len; ///< длина заголовка
const struct SerializeField* fields;
};
#define SERIALIZE_ERR_OK 0 ///< успех
#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти
#define SERIALIZE_ERR_NULL -2 ///< NULL-указатель
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len);
int serialize_decode(const uint8_t* in_buf, size_t in_len,
const struct SerializeSchema* schema,
void** structure);
void serialize_free(const struct SerializeSchema* schema, void** structure);
#endif // SERIALIZE_H
+16 -1
View File
@@ -211,7 +211,7 @@ int socket_set_mark(socket_t sock, int mark) {
}
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
struct sockaddr* dest, socklen_t dest_len) {
const struct sockaddr* dest, socklen_t dest_len) {
#ifdef _WIN32
return sendto(sock, (const char*)buf, (int)len, 0, dest, dest_len);
#else
@@ -230,3 +230,18 @@ ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
return recvfrom(sock, buf, len, 0, src, src_len);
#endif
}
uint16_t ss_get_port(const struct sockaddr_storage *addr) {
if (!addr) return 0;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
return ntohs(ipv4->sin_port);
}
else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
return ntohs(ipv6->sin6_port);
}
return 0;
}
+5 -1
View File
@@ -5,6 +5,7 @@
#ifndef SOCKET_COMPAT_H
#define SOCKET_COMPAT_H
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
@@ -62,11 +63,14 @@ int socket_set_mark(socket_t sock, int mark);
// I/O operations
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
struct sockaddr* dest, socklen_t dest_len);
const struct sockaddr* dest, socklen_t dest_len);
ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
struct sockaddr* src, socklen_t* src_len);
// Utility
const char* socket_strerror(int err);
// взять порт из sockaddr_storage
uint16_t ss_get_port(const struct sockaddr_storage *addr);
#endif // SOCKET_COMPAT_H
+99
View File
@@ -0,0 +1,99 @@
/* sliding_window_min.c */
#include "swm_min.h"
#include <stdlib.h>
#include <limits.h>
struct SlidingWindowMin {
int* data; /* кольцевой буфер значений */
int* deq; /* deque индексов (тоже кольцевой) */
int dq_head;
int dq_tail;
int deq_count;
int cur_pos;
int window_size;
};
SlidingWindowMin* swm_create(int window_size)
{
if (window_size <= 0) {
return NULL;
}
SlidingWindowMin* swm = (SlidingWindowMin*)malloc(sizeof(SlidingWindowMin));
if (!swm) {
return NULL;
}
swm->data = (int*)malloc((size_t)window_size * sizeof(int));
if (!swm->data) {
free(swm);
return NULL;
}
swm->deq = (int*)malloc((size_t)window_size * sizeof(int));
if (!swm->deq) {
free(swm->data);
free(swm);
return NULL;
}
swm->window_size = window_size;
swm->dq_head = 0;
swm->dq_tail = 0;
swm->deq_count = 0;
swm->cur_pos = 0;
return swm;
}
void swm_destroy(SlidingWindowMin* swm)
{
if (swm) {
free(swm->data);
free(swm->deq);
free(swm);
}
}
void swm_add(SlidingWindowMin* swm, int val)
{
if (!swm) return;
int idx = swm->cur_pos;
int w = swm->window_size;
/* записываем в кольцевой буфер */
swm->data[idx % w] = val;
/* 1. удаляем из начала deque индексы, которые уже вышли за окно */
while (swm->deq_count > 0 && swm->deq[swm->dq_head] <= idx - w) {
swm->dq_head = (swm->dq_head + 1) % w;
swm->deq_count--;
}
/* 2. удаляем из хвоста все элементы, которые хуже текущего */
while (swm->deq_count > 0) {
int last_idx = swm->deq[(swm->dq_tail - 1 + w) % w];
if (swm->data[last_idx % w] < val) {
break;
}
swm->dq_tail = (swm->dq_tail - 1 + w) % w;
swm->deq_count--;
}
/* 3. добавляем текущий индекс */
swm->deq[swm->dq_tail] = idx;
swm->dq_tail = (swm->dq_tail + 1) % w;
swm->deq_count++;
swm->cur_pos++;
}
int swm_get_min(const SlidingWindowMin* swm)
{
if (!swm || swm->deq_count == 0) {
return INT_MAX;
}
int min_idx = swm->deq[swm->dq_head];
return swm->data[min_idx % swm->window_size];
}
+41
View File
@@ -0,0 +1,41 @@
/* sliding_window_min.h */
#ifndef SLIDING_WINDOW_MIN_H
#define SLIDING_WINDOW_MIN_H
#include <stddef.h> /* для size_t */
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SlidingWindowMin SlidingWindowMin;
/**
* Создаёт новую структуру скользящего минимума.
* @param window_size размер окна (должен быть > 0)
* @return указатель на структуру или NULL при ошибке выделения памяти
*/
SlidingWindowMin* swm_create(int window_size);
/**
* Уничтожает структуру и освобождает всю динамически выделенную память.
*/
void swm_destroy(SlidingWindowMin* swm);
/**
* Добавляет новое значение в окно.
* После вызова swm_get_min() сразу вернёт минимум последних min(window_size, добавленных) значений.
*/
void swm_add(SlidingWindowMin* swm, int val);
/**
* Возвращает текущий минимум в окне.
* Если окно пустое — возвращает INT_MAX.
*/
int swm_get_min(const SlidingWindowMin* swm);
#ifdef __cplusplus
}
#endif
#endif /* SLIDING_WINDOW_MIN_H */
+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
+112 -85
View File
@@ -11,21 +11,21 @@
#define LEFT_CHILD(i) (2 * (i))
#define RIGHT_CHILD(i) (2 * (i) + 1)
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
if (!h) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
u_free(h);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
if (!h) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
u_free(h);
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
h->size = 0;
h->capacity = initial_capacity;
h->freed_count = 0;
@@ -50,70 +50,81 @@ void timeout_heap_destroy(TimeoutHeap *h) {
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
// Swap with parent
TimeoutEntry temp = h->heap[PARENT(i) - 1];
h->heap[PARENT(i) - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = PARENT(i);
}
static void update_index(TimeoutHeap *h, size_t idx) {
if (h->heap[idx].index_ptr)
*h->heap[idx].index_ptr = idx;
}
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) {
TimeoutEntry temp = h->heap[a];
h->heap[a] = h->heap[b];
h->heap[b] = temp;
update_index(h, a);
update_index(h, b);
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
swap_entries(h, PARENT(i) - 1, i - 1);
i = PARENT(i);
}
}
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
h->heap[idx].deleted = 0;
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
h->heap[idx].index_ptr = index_ptr;
h->heap[idx].deleted = 0;
if (index_ptr)
*index_ptr = idx;
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
// Swap
TimeoutEntry temp = h->heap[smallest - 1];
h->heap[smallest - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = smallest;
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
swap_entries(h, smallest - 1, i - 1);
i = smallest;
}
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
h->heap[0] = h->heap[--h->size];
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
h->heap[0] = h->heap[--h->size];
update_index(h, 0);
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
}
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
@@ -149,18 +160,34 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
return 0;
}
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
for (size_t i = 0; i < h->size; ++i) {
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
h->heap[i].deleted = 1;
return 0;
}
}
return -1; // Not found
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
for (size_t i = 0; i < h->size; ++i) {
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
h->heap[i].deleted = 1;
h->heap[i].expiration = 0;
bubble_up(h, i + 1);
return 0;
}
}
return -1; // Not found
}
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) {
if (index >= h->size || h->heap[index].data != data) return -1;
h->heap[index].deleted = 1;
h->heap[index].expiration = 0;
bubble_up(h, index + 1);
return 0;
}
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
}
size_t timeout_heap_get_size(TimeoutHeap *h) {
if (!h) return 0;
return h->size;
}
+6 -1
View File
@@ -11,6 +11,7 @@ typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
typedef struct {
TimeoutTime expiration; // Sort key (smaller = earlier)
void *data; // User data (e.g., callback or ID)
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
int deleted; // 0 = active, 1 = deleted
} TimeoutEntry;
@@ -53,7 +54,7 @@ void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*call
* @param data User data associated with the timeout.
* @return 0 on success, -1 on allocation failure.
*/
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data);
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr);
/**
* Peek at the earliest non-deleted timeout without removing it.
@@ -82,6 +83,8 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
*/
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data);
/**
* Get the number of freed timer nodes.
* @param h The heap.
@@ -89,4 +92,6 @@ int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
*/
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
size_t timeout_heap_get_size(TimeoutHeap *h);
#endif // TIMEOUT_HEAP_H
+395 -267
View File
@@ -32,25 +32,29 @@
// Timeout node with safe cancellation
struct timeout_node {
char name[16];
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
struct timeout_node* next; // For immediate queue (FIFO)
size_t heap_index; // Position in timeout_heap (SIZE_MAX if not in heap)
};
// Socket node with array-based storage
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
@@ -185,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
@@ -227,11 +233,19 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
// Mark as inactive
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->sockets[index].sock = SOCKET_INVALID;
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
// Mark as inactive and clear all pointers
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->sockets[index].sock = SOCKET_INVALID;
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
sa->sockets[index].read_cbk = NULL;
sa->sockets[index].write_cbk = NULL;
sa->sockets[index].read_cbk_sock = NULL;
sa->sockets[index].write_cbk_sock = NULL;
sa->sockets[index].except_cbk = NULL;
sa->sockets[index].user_data = NULL;
sa->sockets[index].enable_read = 0;
sa->sockets[index].enable_write = 0;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
@@ -319,12 +333,27 @@ uint64_t get_time_tb(void) {
// QueryPerformanceCounter(&count); // Получаем текущее значение счётчика
// return (uint64_t)(count.QuadPart * 10000ULL) / (uint64_t)freq.QuadPart; // Преобразуем в требуемые единицы времени
//}
#else
uint64_t get_time_tb(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
}
#else
uint64_t get_time_tb(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
}
#endif
#ifdef _WIN32
uint64_t get_time_us(void) {
LARGE_INTEGER freq, count;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&count);
return (uint64_t)(count.QuadPart * 1000000ULL / freq.QuadPart);
}
#else
uint64_t get_time_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}
#endif
@@ -421,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);
}
@@ -446,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);
}
@@ -491,32 +521,39 @@ static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback, const char* name) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
struct timeout_node* node = memory_pool_alloc(ua->timeout_pool);
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
node->arg = arg;
node->callback = callback;
node->ua = ua;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
}
ua->timer_alloc_count++;
if (name) {
strncpy(node->name, name, sizeof(node->name) - 1);
node->name[sizeof(node->name) - 1] = '\0';
} else {
node->name[0] = '\0';
}
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->heap_index = SIZE_MAX;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node, &node->heap_index) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
memory_pool_free(ua->timeout_pool, node);
ua->timer_free_count++; // Balance the alloc counter
@@ -538,11 +575,13 @@ void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t cal
}
ua->timer_alloc_count++;
node->name[0] = '\0';
node->arg = user_arg;
node->callback = callback;
node->ua = ua;
node->expiration_ms = 0;
node->next = NULL;
node->heap_index = SIZE_MAX;
// FIFO: добавляем в конец очереди
if (ua->immediate_queue_tail) {
@@ -571,28 +610,29 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id) {
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully marked as deleted - u_free will happen lazily in heap
node->callback = NULL;
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap", node);
return ERR_OK;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// If not found in heap, it may have already expired or been invalid
return ERR_FAIL;
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
if (node->heap_index == SIZE_MAX || node->heap_index >= ua->timeout_heap->size ||
ua->timeout_heap->heap[node->heap_index].data != node) {
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
return ERR_FAIL;
}
if (timeout_heap_cancel_at(ua->timeout_heap, node->heap_index, node) == 0) {
node->heap_index = SIZE_MAX;
node->callback = NULL;
return ERR_OK;
}
return ERR_FAIL;
}
@@ -618,10 +658,10 @@ void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, s
if (read_cbk) ev.events |= EPOLLIN;
if (write_cbk) ev.events |= EPOLLOUT;
// Use level-triggered mode (default) for compatibility with UDP sockets
ev.data.ptr = &ua->sockets->sockets[index];
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
// Failed to add to epoll - remove from socket array and return error
ev.data.fd = fd;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
// Failed to add to epoll - remove from socket array and return error
socket_array_remove(ua->sockets, fd);
ua->socket_alloc_count--;
return NULL;
@@ -675,14 +715,13 @@ void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t
ev.events = 0;
if (read_cbk) ev.events |= EPOLLIN;
if (write_cbk) ev.events |= EPOLLOUT;
ev.data.ptr = &ua->sockets->sockets[index];
// On Windows, need to cast socket_t to int for epoll_ctl
#ifdef _WIN32
int fd = (int)(intptr_t)sock;
#else
int fd = sock;
#endif
// On Windows, need to cast socket_t to int for epoll_ctl
#ifdef _WIN32
int fd = (int)(intptr_t)sock;
#else
int fd = sock;
#endif
ev.data.fd = fd;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
socket_array_remove(ua->sockets, fd);
ua->socket_alloc_count--;
@@ -723,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;
@@ -776,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++;
@@ -797,49 +910,61 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
#if HAS_EPOLL
static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, int n_events) {
for (int i = 0; i < n_events; i++) {
// Check if this is the wakeup fd (data.ptr is NULL)
if (events[i].data.ptr == NULL) {
if (events[i].events & EPOLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
// Socket event
struct socket_node* node = (struct socket_node*)events[i].data.ptr;
if (!node || !node->active) continue;
/* Check for error conditions first */
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLIN) {
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock) {
node->read_cbk_sock(node->sock, node->user_data);
}
} else {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
}
/* Write readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLOUT) {
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->write_cbk_sock) {
node->write_cbk_sock(node->sock, node->user_data);
}
} else {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
// Check if this is the wakeup fd (data.fd is -1)
if (events[i].data.fd < 0) {
if (events[i].events & EPOLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
// Socket event — save node fields locally: callbacks may realloc
// the socket array, invalidating `node`.
struct socket_node* node = socket_array_get(ua->sockets, events[i].data.fd);
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;
int local_type = node->type;
void* local_ud = node->user_data;
socket_callback_t local_except = node->except_cbk;
socket_callback_t local_read = node->read_cbk;
socket_callback_t local_write = node->write_cbk;
socket_t_callback_t local_read_sock = node->read_cbk_sock;
socket_t_callback_t local_write_sock = node->write_cbk_sock;
/* Check for error conditions first */
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
if (local_except) {
local_except(local_fd, local_ud);
}
}
/* Read readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLIN) {
if (local_type == SOCKET_NODE_TYPE_SOCK) {
if (local_read_sock) {
local_read_sock(local_sock, local_ud);
}
} else {
if (local_read) {
local_read(local_fd, local_ud);
}
}
}
/* Write readiness - use appropriate callback based on socket type */
if (events[i].events & EPOLLOUT) {
if (local_type == SOCKET_NODE_TYPE_SOCK) {
if (local_write_sock) {
local_write_sock(local_sock, local_ud);
}
} else {
if (local_write) {
local_write(local_fd, local_ud);
}
}
}
}
}
@@ -850,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
@@ -867,20 +994,19 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
}
// Use minimum of requested and next timer if both finite
struct timeval poll_timeout;
if (timeout_tb < 0) {
// Infinite requested - use next timer if any
poll_timeout = next_timeout;
} else {
// Finite requested - min of requested and next
if (next_timeout.tv_sec < req_timeout.tv_sec ||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
poll_timeout = next_timeout;
} else {
poll_timeout = req_timeout;
}
}
struct timeval poll_timeout;
if (timeout_tb < 0) {
poll_timeout = next_timeout;
} else {
if (next_timeout.tv_sec < req_timeout.tv_sec ||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
poll_timeout = next_timeout;
} else {
poll_timeout = req_timeout;
}
}
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
int timeout_ms;
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {
@@ -971,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);
@@ -1014,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) {
@@ -1072,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) {
@@ -1185,9 +1313,9 @@ struct UASYNC* uasync_create(void) {
ua->immediate_queue_head = NULL;
ua->immediate_queue_tail = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA...");
ua->sockets = socket_array_create(16);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA1...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA...");
ua->sockets = socket_array_create(16);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA1...");
if (!ua->sockets) {
if (ua->wakeup_initialized) {
#ifdef _WIN32
@@ -1239,7 +1367,7 @@ struct UASYNC* uasync_create(void) {
// Set callback to u_free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
// Initialize epoll on Linux
ua->epoll_fd = -1;
ua->use_epoll = 0;
@@ -1252,7 +1380,7 @@ struct UASYNC* uasync_create(void) {
if (ua->wakeup_initialized) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = NULL; // NULL ptr indicates wakeup fd
ev.data.fd = -1; // -1 fd indicates wakeup
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
}
@@ -1262,7 +1390,7 @@ struct UASYNC* uasync_create(void) {
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
#ifdef _WIN32
// Windows: self-connected UDP socket for wakeup
@@ -1308,8 +1436,19 @@ struct UASYNC* uasync_create(void) {
u_free(ua);
return NULL;
}
ua->wakeup_initialized = 1;
ua->wakeup_initialized = 1;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = -1;
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
}
}
#endif
fcntl(ua->wakeup_pipe[0], F_SETFL, fcntl(ua->wakeup_pipe[0], F_GETFL, 0) | O_NONBLOCK);
fcntl(ua->wakeup_pipe[1], F_SETFL, fcntl(ua->wakeup_pipe[1], F_GETFL, 0) | O_NONBLOCK);
@@ -1319,84 +1458,95 @@ struct UASYNC* uasync_create(void) {
pthread_mutex_init(&ua->posted_lock, NULL);
#endif
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
printf(" Timer: node=%p, expires=%llu ms\n",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration);
}
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: NULL uasync instance", prefix);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: UASYNC Resource Report for %p", prefix, ua);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
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++) {
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 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, deleted: %zu, total: %zu",
active_timers, deleted_timers, ua->timeout_heap->size);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket array capacity: %d, active: %d",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket: fd=%d, active=%d",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Total active sockets: %d", active_sockets);
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: End of resource report", prefix);
}
// Modified function in u_async.c: uasync_destroy
// Changes: Close wakeup sockets properly on Windows.
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts
// Очистить immediate_queue
@@ -1412,7 +1562,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
// Очистить heap
if (ua->timeout_heap) {
size_t u_freed_count = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
@@ -1512,31 +1661,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
socket_platform_cleanup();
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_pool) {
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
}
if (!ua->immediate_queue_head) {
ua->immediate_queue_head = NULL;
ua->immediate_queue_tail = NULL;
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_u_free, size_t* socket_alloc, size_t* socket_u_free) {
if (!ua) return;
@@ -1625,8 +1749,12 @@ int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id) {
return (*socket_id != NULL) ? 0 : -1;
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1); // Infinite wait
}
void uasync_stop(struct UASYNC* ua) {
if (ua) ua->stop = 1;
}
void uasync_mainloop(struct UASYNC* ua) {
while (!ua->stop) {
uasync_poll(ua, -1);
}
}
+22 -9
View File
@@ -5,9 +5,10 @@
#ifndef UASYNC_H
#define UASYNC_H
#include "platform_compat.h"
#include <stddef.h>
#include "timeout_heap.h"
#include "platform_compat.h"
#include <stddef.h>
#include <signal.h>
#include "timeout_heap.h"
#include "socket_compat.h"
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
@@ -66,8 +67,9 @@ struct UASYNC {
#ifdef _WIN32
CRITICAL_SECTION posted_lock;
#else
pthread_mutex_t posted_lock;
#endif
pthread_mutex_t posted_lock;
#endif
volatile sig_atomic_t stop;
};
// Type definitions
@@ -77,13 +79,19 @@ typedef struct UASYNC UASYNC_t;
// Instance API - основной API для работы с uasync
struct UASYNC* uasync_create(void);
void uasync_destroy(struct UASYNC* ua, int close_fds);
void uasync_init_instance(struct UASYNC* ua);
// текущее время (timebase 0.1ms)
uint64_t get_time_tb(void);
// текущее время в микросекундах (для burst-измерений)
uint64_t get_time_us(void);
// Timeouts, timebase = 0.1 mS
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
// Как работать с таймаутами если таймаут может быть использован несколько раз:
// 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);
// Immediate execution in next mainloop (FIFO order)
@@ -94,14 +102,19 @@ 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);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(struct UASYNC* ua);
void uasync_stop(struct UASYNC* ua);
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
+219
View File
@@ -0,0 +1,219 @@
Диаграмма вызовов функций при определении типа NAT (NAT detection)
=====================================================================
=== ИНИЦИАЛИЗАЦИЯ BGP ===
route_bgp_init(instance)
└── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL)
└── При создании нового ETCP_CONN:
route_bgp_etcp_conn_cbk(conn, arg)
├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp)
└── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_REQUEST
├── etcp_connection_create(instance, name) [если новый peer]
│ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk]
├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1)
├── Отправляет ETCP_INIT_RESPONSE (с NAT info)
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── conn->ready_cbk
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_RESPONSE
├── Сохраняет NAT address: link->nat_ip, link->nat_port
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) ===
route_bgp_on_conn_up(etcp, arg)
└── route_bgp_new_conn(conn)
├── route_bgp_add_to_senders(bgp, conn)
└── Сканирует ВСЕ линки ВСЕХ соединений:
if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS)
└── route_bgp_start_link_nat_check(bgp, link)
├── if (nat_check_status == IN_PROGRESS) return
├── route_bgp_find_third_node(bgp, exclude=link->etcp)
│ └── Ищет другой ETCP_CONN в senders_list
├── Определяет target_ip/target_port:
│ ├── Если link->nat_ip != 0: использует nat_ip/nat_port
│ └── Иначе: использует link->remote_addr
├── Проверяет is_local_subnet(target_ip) [если запрещено - skip]
├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port)
│ └── Ищет первый линк с nat_ip != 0
├── Создает nat_check_arg { link, nat_ip, nat_port }
└── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port,
count=3, interval=500ms, timeout=1000ms,
wait_timeout=5000ms,
cb=nat_link_check_cb, arg=nat_check_arg,
pubkey=peer_pubkey)
=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) ===
route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...)
├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey }
├── etcp_send(to_conn, entry) [отправляет запрос third node]
├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer }
└── Добавляет в bgp->ping_pending
=== ОБРАБОТКА PING_REQ (на third node) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_REQ
└── route_ping_handle_req(bgp, from_conn, data, len)
├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total }
├── Находит первый IPv4 ETCP_SOCKET -> local_sock
└── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr,
timeout_ms, route_ping_single_cb, ctx, NULL, 0)
└── Отправляет ETCP пинг на target_addr
=== ОТВЕТ НА ПИНГ (целевой узел) ===
etcp_connections_read_callback_socket()
└── Получает ETCP пинг -> отправляет pong
=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) ===
route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len)
├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt
├── Если count_sent < count_total:
│ └── etcp_send_ping_to_socket(...) [следующий пинг]
└── Иначе:
└── route_ping_series_finish(ctx)
├── Вычисляет avg_rtt
└── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt }
└── etcp_send(reply_conn, entry) [отправляет результат инициатору]
=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_RESP
└── route_ping_handle_resp(bgp, from_conn, data, len)
├── Ищет route_ping_pending по request_id
├── Отменяет timeout_timer
└── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
=== CALLBACK ПРОВЕРКИ NAT ===
nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED
├── link->nat_type = nat_type
├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED
└── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type)
├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type }
└── etcp_send(conn, entry) [отправляет результат клиенту]
=== ПРИЕМ NAT_INFO (на клиенте) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_NAT_INFO
└── route_bgp_handle_nat_info(bgp, from_conn, data, len)
├── Ищет линки по remote_socket_id
├── Устанавливает link->nat_type = info->nat_type
└── Логирует результат NAT detection
=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) ===
route_bgp_send_nat_check_req(conn, socket_id)
├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port }
│ ├── Ищет линк с remote_socket_id == socket_id
│ └── Берет interface_addr из линка -> interface_ip/interface_port
└── etcp_send(conn, entry)
route_bgp_handle_nat_check_req(bgp, from_conn, data, len)
├── Принимает BGP_NAT_CHECK_REQ
├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy
├── Ищет линк с remote_socket_id == req->socket_id
└── Если линк найден и не в процессе проверки:
└── route_bgp_start_link_nat_check(bgp, target_link)
[далее по стандартному flow]
=== СВЯЗЬ СТРУКТУР ===
struct ETCP_SOCKET
├── local_addr [адрес бинда из конфига]
├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг]
└── ...
struct ETCP_LINK
├── remote_addr [адрес удаленного узла]
├── nat_ip [NAT IP, полученный из INIT_RESPONSE]
├── nat_port [NAT port, полученный из INIT_RESPONSE]
├── nat_type [OPEN/RESTRICTED, результат проверки]
├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED]
├── initialized [0/1, флаг завершения handshake]
└── ...
struct ETCP_CONN
├── links [список ETCP_LINK]
├── up_cbk [route_bgp_on_conn_up]
├── down_cbk [route_bgp_on_conn_down]
├── ready_cbk [вызывается при etcp_conn_ready]
├── initialized [0/1, флаг готовности]
├── links_up [0/1, есть ли активные линки]
└── ...
=== СТАТУСЫ NAT CHECK ===
NAT_CHECK_NONE = 0 [не проверялся]
NAT_CHECK_WAITING = 1 [ожидает]
NAT_CHECK_IN_PROGRESS = 2 [проверка запущена]
NAT_CHECK_OPEN = 3 [результат: OPEN]
NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED]
NAT_TYPE_UNKNOWN = 0
NAT_TYPE_OPEN = 1
NAT_TYPE_RESTRICTED = 2
NAT_VERIFIED_UNKNOWN = 4
NAT_VERIFIED_OPEN = 5
NAT_VERIFIED_RESTRICTED = 6
NAT_VERIFIED_DIRECT = 7
=== СООБЩЕНИЯ ПРОТОКОЛА ===
ETCP_INIT_REQUEST (0x02) [клиент -> сервер]
ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info]
ETCP_INIT_REQUEST_NOINIT (0x04)
ETCP_INIT_RESPONSE_NOINIT (0x05)
ETCP_PING (0x06)
ETCP_PONG (0x07)
ROUTE_SUBCMD_NODEINFO (0x04)
ROUTE_SUBCMD_REQUEST_TABLE (0x05)
ROUTE_SUBCMD_WITHDRAW (0x06)
ROUTE_SUBCMD_PING_REQ (0x07)
ROUTE_SUBCMD_PING_RESP (0x08)
ROUTE_SUBCMD_NAT_INFO (0x09)
ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A)
=== ПОЛНЫЙ FLOW (одна строка) ===
Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info
NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow]
+1 -1
View File
@@ -376,7 +376,7 @@ static void socket_callback(int fd, void *user_arg) {
u_free(packet);
return;
}
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet);
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet, "netem_delay");
}
// Callback for sending delayed packet
+2408
View File
File diff suppressed because it is too large. Load diff
+19 -1
View File
@@ -7,8 +7,11 @@ utun_CORE_SOURCES = \
config_parser.c \
config_updater.c \
route_lib.c \
route6_lib.c \
route_bgp.c \
route_ping.c \
route_node.c \
route_connectivity.c \
routing.c \
tun_if.c \
tun_route.c \
@@ -17,15 +20,29 @@ utun_CORE_SOURCES = \
tun_windows.c \
etcp.c \
etcp_connections.c \
etcp_bbr.c \
etcp_loadbalancer.c \
etcp_debug.c \
etcp_dump.c \
secure_channel.c \
crc32.c \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
control_server.c \
firewall.c
firewall.c \
eim_nat.c \
nat_transport.c \
dummynet.c \
proxy/tcp_proxy_client.c \
etcp_router.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 \
lwip_tcp/lwip_tcp_out.c
# Platform-specific TUN libs (Windows only)
utun_TUN_LIBS = @TUN_LIBS@
@@ -56,6 +73,7 @@ endif
# Include paths
utun_CORE_CFLAGS = \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/src/uip \
-I$(top_srcdir)/tinycrypt/lib/include \
-I$(top_srcdir)/tinycrypt/lib/source \
-g \
+357 -44
View File
@@ -13,6 +13,7 @@
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#endif
#include "../lib/mem.h"
@@ -23,6 +24,16 @@
static int assign_string(char *dest, size_t dest_size, const char *src);
static int parse_firewall_rule(const char *rule_str, struct global_config *global);
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
for (size_t i = 0; i < binary_len; i++) {
unsigned int byte;
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
binary[i] = (uint8_t)byte;
}
return 0;
}
typedef enum {
SECTION_UNKNOWN,
SECTION_GLOBAL,
@@ -30,7 +41,12 @@ typedef enum {
SECTION_CLIENT,
SECTION_ROUTING,
SECTION_DEBUG,
SECTION_FIREWALL
SECTION_FIREWALL,
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER
} section_type_t;
static char* trim(char *str) {
@@ -109,9 +125,21 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
char clean_addr[MAX_ADDR_LEN];
const char *a = addr_str;
if (addr_str[0] == '[') {
size_t len = strlen(addr_str);
if (len > 2 && addr_str[len - 1] == ']') {
if (len - 2 >= sizeof(clean_addr)) return -1;
memcpy(clean_addr, addr_str + 1, len - 2);
clean_addr[len - 2] = '\0';
a = clean_addr;
}
}
struct addrinfo *result;
if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) {
if (getaddrinfo(a, port_str, &hints, &result) != 0) {
return -1;
}
@@ -258,6 +286,51 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
return 0;
}
static int parse_control_allow(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
trim(rule_copy);
uint8_t cidr = 32;
char *slash = strchr(rule_copy, '/');
if (slash) {
*slash = '\0';
cidr = (uint8_t)atoi(slash + 1);
if (cidr > 32) cidr = 32;
}
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr);
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
if (!new_allows) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows");
return -1;
}
global->control_allows = new_allows;
}
struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count];
rule->network = ip & mask;
rule->netmask = mask;
global->control_allow_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr);
return 0;
}
static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) {
struct CFG_SERVER *srv = servers;
while (srv) {
@@ -269,7 +342,7 @@ static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const
return NULL;
}
static int parse_global(const char *key, const char *value, struct global_config *global) {
static int parse_global(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "my_node_name") == 0) {
return assign_string(global->name, sizeof(global->name), value);
}
@@ -303,29 +376,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "control_ip") == 0) {
// Store control_ip for later processing with control_port
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
if (strcmp(key, "bbr_max_cwnd") == 0) {
global->bbr_max_cwnd = atoi(value);
return 0;
}
if (strcmp(key, "control_port") == 0) {
// Use control_ip from config, fallback to localhost if not set
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
}
if (strcmp(key, "net_debug") == 0) {
global->net_debug = atoi(value);
}
if (strcmp(key, "debug_level") == 0) {
return assign_string(global->debug_level, sizeof(global->debug_level), value);
}
@@ -353,11 +407,189 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, 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;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) {
static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
return 0;
}
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
}
if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) {
return parse_control_allow(value, global);
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown control option '%s'. Valid: ip/control_ip, port/control_port, allow/control_allow", filename, line_num, key);
return -1;
}
static int parse_tcp_proxy_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_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_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
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_client_mtu = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
return -1;
}
char buf[256];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* local_port_str = strtok(buf, " ");
char* arrow = strtok(NULL, " ");
char* remote_str = strtok(NULL, "");
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_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_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_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_client forward remote port: %s", remote_port_str); return -1; }
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_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
return -1;
}
static int parse_nat(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "tun_ifname") == 0) {
strncpy(global->nat_tun_ifname, value, sizeof(global->nat_tun_ifname) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
uint8_t netmask;
parse_ip_with_netmask(value, &global->nat_tun_ip, &netmask);
return 0;
}
if (strcmp(key, "nat_via") == 0) {
global->nat_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "port_start") == 0) {
global->nat_port_start = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "port_end") == 0) {
global->nat_port_end = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->nat_forward_count >= MAX_NAT_FORWARDS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many NAT forward rules (max %d)", MAX_NAT_FORWARDS);
return -1;
}
// Format: proto:internal_ip:internal_port:external_port
// Example: tcp:192.168.1.100:8080:8080
char buf[128];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* proto_str = strtok(buf, ":");
char* ip_str = strtok(NULL, ":");
char* int_port = strtok(NULL, ":");
char* ext_port = strtok(NULL, ":");
if (!proto_str || !ip_str || !int_port || !ext_port) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward format: %s (expected proto:ip:internal_port:external_port)", value);
return -1;
}
uint8_t proto;
if (strcasecmp(proto_str, "tcp") == 0) proto = IPPROTO_TCP;
else if (strcasecmp(proto_str, "udp") == 0) proto = IPPROTO_UDP;
else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward proto: %s (tcp/udp)", proto_str);
return -1;
}
struct in_addr addr;
if (inet_pton(AF_INET, ip_str, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward IP: %s", ip_str);
return -1;
}
int idx = global->nat_forward_count;
global->nat_forwards[idx].proto = proto;
global->nat_forwards[idx].internal_ip_host = ntohl(addr.s_addr);
global->nat_forwards[idx].internal_port_net = htons((uint16_t)atoi(int_port));
global->nat_forwards[idx].external_port = (uint16_t)atoi(ext_port);
global->nat_forward_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "NAT forward: %s %s:%s -> :%s",
proto_str, ip_str, int_port, ext_port);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown NAT option '%s'. Valid: tun_ifname, tun_ip, nat_via, port_start, port_end, forward", filename, line_num, key);
return -1;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv, const char *filename, int line_num) {
if (strcmp(key, "addr") == 0) {
if (strncmp(value, "temporary_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_TEMPORARY;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 0;
}
if (strncmp(value, "permanent_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_PERMANENT;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 0;
}
return parse_address_and_port(value, &srv->ip);
}
if (strcmp(key, "so_mark") == 0) {
@@ -388,16 +620,15 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->mtu = atoi(value);
return 0;
}
if (strcmp(key, "loss_rate") == 0) {
srv->loss_rate = atoi(value);
if (srv->loss_rate < 0) srv->loss_rate = 0;
if (srv->loss_rate > 100) srv->loss_rate = 100;
if (strcmp(key, "only_local") == 0) {
srv->only_local = atoi(value) ? 1 : 0;
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
return -1;
}
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) {
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers, const char *filename, int line_num) {
if (strcmp(key, "link") == 0) {
char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN];
if (strlen(value) >= sizeof(link_copy)) return -1;
@@ -435,7 +666,8 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
return 0;
}
return 0;
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown client option '%s'. Valid: link, peer_public_key, keepalive", filename, line_num, key);
return -1;
}
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
@@ -455,6 +687,11 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy_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;
@@ -482,9 +719,13 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// Set default values
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
cfg->global.control_allows = NULL;
cfg->global.control_allow_count = 0;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -543,18 +784,18 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
switch (cur_section) {
case SECTION_GLOBAL:
if (parse_global(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid global key '%s'", filename, line_num, key);
if (parse_global(key, value, &cfg->global, filename, line_num) < 0) {
// parse_global already printed the error
}
break;
case SECTION_SERVER:
if (cur_server && parse_server(key, value, cur_server) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid server key '%s'", filename, line_num, key);
if (cur_server && parse_server(key, value, cur_server, filename, line_num) < 0) {
// parse_server already printed the error
}
break;
case SECTION_CLIENT:
if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid client key '%s'", filename, line_num, key);
if (cur_client && parse_client(key, value, cur_client, cfg->servers, filename, line_num) < 0) {
// parse_client already printed the error
}
break;
case SECTION_ROUTING:
@@ -562,6 +803,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
add_route_entry(&cfg->route_subnets, value);
} else if (strcmp(key, "my_subnet") == 0) {
add_route_entry(&cfg->my_subnets, value);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown routing option '%s'. Valid: route_subnet, my_subnet", filename, line_num, key);
}
break;
case SECTION_DEBUG:
@@ -581,8 +824,54 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
if (parse_firewall_rule(value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown firewall option '%s'. Valid: allow", filename, line_num, key);
}
break;
case SECTION_CONTROL:
if (parse_control(key, value, &cfg->global, filename, line_num) < 0) {
// parse_control already printed the error
}
break;
case SECTION_ALLOWED_KEYS:
if (strcmp(key, "allow_all") == 0) {
cfg->global.allowed_keys_allow_all = atoi(value) ? 1 : 0;
} else if (strcmp(key, "key") == 0) {
struct CFG_ALLOWED_KEY *new_key = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY));
if (!new_key) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate allowed key");
} else if (strlen(value) != SC_PUBKEY_SIZE * 2) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key length %zu, expected %d", filename, line_num, strlen(value), SC_PUBKEY_SIZE * 2);
u_free(new_key);
} else if (hex_to_binary(value, new_key->key_bin, SC_PUBKEY_SIZE) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid hex key format", filename, line_num);
u_free(new_key);
} else {
new_key->next = cfg->global.allowed_keys;
cfg->global.allowed_keys = new_key;
cfg->global.allowed_keys_count++;
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'. Valid: allow_all, key", filename, line_num, key);
}
break;
case SECTION_NAT:
cfg->global.nat_enabled = 1;
if (parse_nat(key, value, &cfg->global, filename, line_num) < 0) {
// parse_nat already printed the error
}
break;
case SECTION_TCP_PROXY_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_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:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@@ -657,6 +946,15 @@ void free_config(struct utun_config *config) {
// Free firewall rules
u_free(config->global.firewall_rules);
// Free control allow rules (array allocated with realloc)
u_free(config->global.control_allows);
// Free allowed keys
struct CFG_ALLOWED_KEY *ak = config->global.allowed_keys;
while (ak) {
struct CFG_ALLOWED_KEY *next = ak->next;
u_free(ak);
ak = next;
}
u_free(config);
}
@@ -678,20 +976,20 @@ void print_config(const struct utun_config *cfg) {
const struct global_config *g = &cfg->global;
char ip_buffer[64];
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d, net_debug=%d",
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d",
g->my_private_key_hex, g->my_public_key_hex,
(unsigned long long)g->my_node_id,
g->tun_ifname[0] ? g->tun_ifname : "auto",
ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)),
g->mtu, g->keepalive_timeout, g->tun_test_mode, g->net_debug);
g->mtu, g->keepalive_timeout, g->tun_test_mode);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
struct CFG_SERVER *s = cfg->servers;
while (s) {
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d, loss_rate=%d%%)",
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
s->so_mark, s->netif_index, s->type, s->mtu, s->loss_rate);
s->so_mark, s->netif_index, s->type, s->mtu);
s = s->next;
}
@@ -741,6 +1039,21 @@ void print_config(const struct utun_config *cfg) {
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Allowed keys: allow_all=%d, count=%d", g->allowed_keys_allow_all, g->allowed_keys_count);
if (!g->allowed_keys_allow_all && g->allowed_keys_count > 0) {
struct CFG_ALLOWED_KEY *ak = g->allowed_keys;
while (ak) {
char key_hex[SC_PUBKEY_SIZE * 2 + 1];
for (int i = 0; i < SC_PUBKEY_SIZE; i++) {
snprintf(key_hex + i * 2, sizeof(key_hex) - i * 2, "%02x", ak->key_bin[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " key=%s", key_hex);
ak = ak->next;
}
}
}
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
+63 -3
View File
@@ -5,6 +5,7 @@
#include <stdint.h>
#include <stddef.h>
#include "../lib/platform_compat.h"
#include "secure_channel.h"
#ifdef __cplusplus
extern "C" {
@@ -27,6 +28,10 @@ struct IP {
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PRIVATE 3
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
#define CFG_IPV6_MODE_PERMANENT 2 // permanent IPv6 from interface
struct CFG_SERVER {
struct CFG_SERVER* next;
char name[MAX_CONN_NAME_LEN];
@@ -35,8 +40,9 @@ struct CFG_SERVER {
int so_mark;
int fib; // FreeBSD FIB (routing table), -1 = don't set
uint8_t type; // public/nat/private
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
int mtu;
int loss_rate; // packet loss rate in percent (0-100), default 0
uint8_t only_local; // 1 = only local connections, no forwarding
};
struct CFG_CLIENT_LINK {
@@ -66,6 +72,23 @@ struct CFG_FIREWALL_RULE {
uint8_t bypass; // 1 for allow=all (bypass all checks)
};
struct CFG_CONTROL_ALLOW {
uint32_t network; // IPv4 network in host byte order
uint32_t netmask; // netmask in host byte order (e.g. 0xffffff00 for /24)
};
struct CFG_ALLOWED_KEY {
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
struct CFG_ALLOWED_KEY *next;
};
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
struct tcp_proxy_client_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
};
struct global_config {
char name[16]; // Instance name
char my_private_key_hex[MAX_KEY_LEN];
@@ -76,8 +99,7 @@ struct global_config {
int mtu;
struct sockaddr_storage control_sock;
char control_ip[MAX_ADDR_LEN]; // Control server IP address
int net_debug;
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
char debug_level[16]; // debug level: error, warn, info, debug, trace
@@ -96,11 +118,49 @@ 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 bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
// Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules;
int firewall_rule_count;
int firewall_bypass_all;
// Control server configuration ([control] section)
struct CFG_CONTROL_ALLOW *control_allows;
int control_allow_count;
// Allowed keys configuration ([allowed_keys] section)
struct CFG_ALLOWED_KEY *allowed_keys;
int allowed_keys_count;
int allowed_keys_allow_all;
// NAT configuration ([nat] section)
int nat_enabled;
char nat_tun_ifname[16];
struct IP nat_tun_ip;
uint64_t nat_via_node_id; // 0 = this node is provider; !=0 = client, use this provider
uint16_t nat_port_start;
uint16_t nat_port_end;
#define MAX_NAT_FORWARDS 64
struct {
uint8_t proto;
uint32_t internal_ip_host;
uint16_t internal_port_net;
uint16_t external_port;
} nat_forwards[MAX_NAT_FORWARDS];
int nat_forward_count;
// TCP proxy 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;
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
};
struct utun_config {
+4 -4
View File
@@ -257,10 +257,10 @@ int config_ensure_keys_and_node_id(const char *filename) {
// Debug: print what we found
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Checking config - priv_key='%s' (len=%zu), pub_key='%s' (len=%zu), node_id=%016llx",
global->my_private_key_hex ? global->my_private_key_hex : "NULL",
global->my_private_key_hex ? strlen(global->my_private_key_hex) : 0,
global->my_public_key_hex ? global->my_public_key_hex : "NULL",
global->my_public_key_hex ? strlen(global->my_public_key_hex) : 0,
global->my_private_key_hex[0] ? global->my_private_key_hex : "NULL",
global->my_private_key_hex[0] ? strlen(global->my_private_key_hex) : 0,
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL",
global->my_public_key_hex[0] ? strlen(global->my_public_key_hex) : 0,
(unsigned long long)global->my_node_id);
+293 -11
View File
@@ -10,8 +10,12 @@
#include "etcp_connections.h"
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "pkt_normalizer.h"
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/u_async.h"
#include "../lib/timeout_heap.h"
#include "../lib/memory_pool.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
@@ -87,7 +91,9 @@ static void handle_client_data(struct control_server* server, struct control_cli
static void close_client(struct control_server* server, struct control_client* client);
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_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);
@@ -269,6 +275,24 @@ void control_server_shutdown(struct control_server* server) {
* Client Connection Handling
* ============================================================================ */
static int is_control_ip_allowed(const struct control_server* server, uint32_t client_ip) {
if (!server || !server->instance || !server->instance->config) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control IP check: no config available");
return 0;
}
const struct global_config *g = &server->instance->config->global;
if (g->control_allow_count == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied (no allow rules) - add control_allow=IP/mask to [control] in config");
return 0;
}
for (int i = 0; i < g->control_allow_count; i++) {
const struct CFG_CONTROL_ALLOW *r = &g->control_allows[i];
if ((client_ip & r->netmask) == r->network) return 1;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied from IP (not in allow list) - add control_allow=IP/mask to [control]");
return 0;
}
static void accept_callback(socket_t fd, void* arg) {
struct control_server* server = (struct control_server*)arg;
@@ -317,6 +341,28 @@ static void accept_callback(socket_t fd, void* arg) {
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
}
#endif
/* Check allowed IP (default deny all) */
uint32_t client_ip = 0;
if (client_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
client_ip = ntohl(sin->sin_addr.s_addr);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "IPv6 not supported for control allow list");
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
if (!is_control_ip_allowed(server, client_ip)) {
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Accept...");
/* Check max clients */
@@ -487,8 +533,11 @@ static void client_read_callback(socket_t fd, void* arg) {
}
client->recv_len += received;
/* Process messages */
if (client->recv_len > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow");
if (server) close_client(server, client);
return;
}
if (server) {
handle_client_data(server, client);
}
@@ -560,6 +609,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
while (client->recv_len >= sizeof(struct etcpmon_msg_header)) {
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer;
if (hdr->size == 0 || hdr->size > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message size from client: %u", hdr->size);
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Validate header */
if (etcpmon_validate_header(hdr) != 0) {
if (server->log_file) {
@@ -605,6 +665,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client selected connection: %016llX",
(unsigned long long)cmd->peer_node_id);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SELECT_CONN payload size %u", payload_size);
close_client(server, client);
return;
}
break;
@@ -617,6 +681,46 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_LIST_SOCKETS:
send_socket_list(server, client, req_seq);
break;
case ETCPMON_CMD_ACTION:
if (payload_size >= sizeof(struct etcpmon_cmd_action)) {
struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload;
if (strncmp(cmd->action, "nat", 3) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
route_bgp_request_nat_check_all(server->instance->bgp);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
}
}
break;
case ETCPMON_CMD_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",
@@ -627,14 +731,9 @@ static void handle_client_data(struct control_server* server, struct control_cli
return;
default:
if (server->log_file) {
fprintf(server->log_file, "%llu: [ERROR] Unknown command from client: 0x%02X\n",
(unsigned long long)get_timestamp_ms(), hdr->type);
fflush(server->log_file);
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
send_error(client, ETCPMON_ERR_INVALID_CMD, "Unknown command", req_seq);
break;
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Remove processed message from buffer */
@@ -705,6 +804,104 @@ static void send_conn_list(struct control_server* server, struct control_client*
u_free(buffer);
}
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct UTUN_INSTANCE* instance = server->instance;
if (!instance || !instance->etcp_sockets) {
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(0);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list),
ETCPMON_RSP_SOCKET_LIST, seq_id);
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
rsp->count = 0;
send(client->fd, buffer, rsp_size, 0);
u_free(buffer);
return;
}
uint8_t count = 0;
struct ETCP_SOCKET* sock = instance->etcp_sockets;
while (sock && count < ETCPMON_MAX_CONNECTIONS) {
count++;
sock = sock->next;
}
if (count > ETCPMON_MAX_CONNECTIONS) count = ETCPMON_MAX_CONNECTIONS;
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(count);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate socket list buffer");
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list) +
count * sizeof(struct etcpmon_socket_info),
ETCPMON_RSP_SOCKET_LIST,
seq_id);
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
rsp->count = count;
struct etcpmon_socket_info* info = (struct etcpmon_socket_info*)(buffer + sizeof(*hdr) + sizeof(*rsp));
sock = instance->etcp_sockets;
for (uint8_t i = 0; i < count && sock; i++) {
strncpy(info[i].name, sock->name, ETCPMON_MAX_CONN_NAME - 1);
info[i].name[ETCPMON_MAX_CONN_NAME - 1] = '\0';
info[i].is_ipv6 = (sock->interface_addr.ss_family == AF_INET6) ? 1 : 0;
if (sock->interface_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->interface_addr;
inet_ntop(AF_INET, &sin->sin_addr, info[i].ip, sizeof(info[i].ip));
info[i].port = ntohs(sin->sin_port);
} else if (sock->interface_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->interface_addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].ip, sizeof(info[i].ip));
info[i].port = ntohs(sin6->sin6_port);
} else {
strncpy(info[i].ip, "unknown", sizeof(info[i].ip) - 1);
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
info[i].port = 0;
}
info[i].nat_type = sock->nat_type;
/* NAT address from socket (set when INIT_RESPONSE received) */
info[i].nat_ip[0] = '\0';
info[i].nat_port = 0;
if (sock->nat_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->nat_addr;
inet_ntop(AF_INET, &sin->sin_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin->sin_port);
} else if (sock->nat_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->nat_addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin6->sin6_port);
}
sock = sock->next;
}
if (server->log_file) {
log_hex_data(server->log_file, "TX", buffer, rsp_size);
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
(unsigned long long)get_timestamp_ms(), count);
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
}
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct UTUN_INSTANCE* instance = server->instance;
@@ -823,10 +1020,21 @@ static void send_metrics(struct control_server* server, struct control_client* c
rsp->etcp.cnt_ack_hit_sndq = conn->cnt_ack_hit_sndq;
rsp->etcp.cnt_ack_miss = conn->cnt_ack_miss;
rsp->etcp.cnt_link_wait = conn->cnt_link_wait;
rsp->etcp.rx_dup_count = conn->rx_dup_count;
rsp->etcp.tx_dup_count = conn->tx_dup_count;
rsp->etcp.tx_state = conn->tx_state;
for (int i = 0; i < 8; i++) {
rsp->etcp.debug[i] = conn->debug[i];
}
/* System resources */
if (instance->ua && instance->ua->timeout_heap) {
rsp->etcp.active_timeouts = (uint32_t)timeout_heap_get_size(instance->ua->timeout_heap);
} else {
rsp->etcp.active_timeouts = 0;
}
rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks());
/* Fill TUN metrics */
if (instance->tun) {
@@ -857,6 +1065,15 @@ static void send_metrics(struct control_server* server, struct control_client* c
rsp->tun.rt_local = 0;
rsp->tun.rt_learned = 0;
}
/* BGP routing */
if (instance->bgp) {
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
} else {
rsp->tun.rt_bgp_senders = 0;
rsp->tun.rt_bgp_nodes = 0;
}
} else {
memset(&rsp->tun, 0, sizeof(rsp->tun));
}
@@ -876,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;
@@ -885,6 +1102,26 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].keepalive_recv = (uint32_t)link->keepalive_recv_count;
link_info[i].inflight_bytes = link->inflight_bytes;
link_info[i].inflight_packets = link->inflight_packets;
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
/* 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;
}
@@ -942,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
* ============================================================================ */
+88 -10
View File
@@ -10,6 +10,7 @@
#include "../lib/socket_compat.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include "etcp_connections.h"
#include <stdlib.h>
#include <string.h>
@@ -130,7 +131,6 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
}
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data;
ssize_t n = socket_sendto(dn->sock, pkt->data, pkt->len,
(struct sockaddr*)&dir->dest_addr, dir->dest_len);
if (n < 0) {
@@ -149,7 +149,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
int delay_tb = 0;
if (dir->bandwidth_kbps > 0) {
uint64_t delay_calc = (uint64_t)bytes_sent * 80000ULL / (uint64_t)dir->bandwidth_kbps;
uint64_t delay_calc = (uint64_t)bytes_sent * 80ULL / (uint64_t)dir->bandwidth_kbps;
delay_tb = (int)delay_calc;
if (delay_tb == 0) delay_tb = 1;
}
@@ -158,7 +158,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
if (sarg) {
sarg->dn = dn;
sarg->dir_idx = dir_idx;
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback);
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper");
if (dir->shaper_timer) {
dir->shaper_timer_set++;
} else {
@@ -270,7 +270,7 @@ static void dummynet_read_callback(socket_t sock, void* user_arg) {
targ->dn = dn;
targ->entry = entry;
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback);
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback, "dummynet_delay");
if (!timer) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to set delay timer");
dir->stats.dropped++;
@@ -304,7 +304,7 @@ static void dummynet_delay_callback(void* user_arg) {
/* Добавляем в очередь */
uint32_t id = (uint32_t)(uintptr_t)entry;
if (queue_data_put(dir->queue, entry, id) != 0) {
if (queue_data_put(dir->queue, entry) != 0) {
dir->stats.dropped++;
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx);
queue_entry_free(entry);
@@ -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;
@@ -326,7 +323,15 @@ static void dummynet_delay_callback(void* user_arg) {
if (sarg) {
sarg->dn = dn;
sarg->dir_idx = dir_idx;
dir->shaper_timer = uasync_set_timeout(dn->ua, 0, sarg, dummynet_shaper_callback);
int shaper_delay_tb = 0;
if (dir->bandwidth_kbps > 0) {
uint64_t delay_calc = (uint64_t)pkt->len * 80ULL / (uint64_t)dir->bandwidth_kbps;
shaper_delay_tb = (int)delay_calc;
if (shaper_delay_tb == 0) shaper_delay_tb = 1;
}
dir->shaper_timer = uasync_set_timeout(dn->ua, shaper_delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper0");
if (dir->shaper_timer) dir->shaper_timer_set++;
else { u_free(sarg); dir->shaper_pending = 0; }
} else {
dir->shaper_pending = 0;
}
@@ -359,7 +364,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
/* Создаём очереди для направлений */
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) {
dn->dirs[i].queue = queue_new(ua, 0, "dummynet1");
dn->dirs[i].queue = queue_new(ua, 0, 0, 0, "dummynet1");
if (!dn->dirs[i].queue) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create queue for direction %d", i);
dummynet_destroy(dn);
@@ -562,3 +567,76 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
*shaper_set = dir->shaper_timer_set;
*shaper_fire = dir->shaper_timer_fire;
}
// === Inline filter: send_hook для etcp_udp_send ===
struct dummynet_filter {
struct UASYNC* ua;
uint32_t loss_permille;
uint32_t delay_ms;
uint32_t jitter_ms;
struct ETCP_LINK* link;
struct dummynet_stats stats;
};
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* ctx) {
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
(void)link;
df->stats.recv++;
if (df->loss_permille > 0) {
uint32_t roll = (uint32_t)rand() % 1000;
if (roll < df->loss_permille) {
df->stats.lost++;
return (ssize_t)len;
}
}
ssize_t sent = socket_sendto(fd, buf, len, addr, addr_len);
if (sent > 0) df->stats.sent++;
return sent;
}
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua) {
struct dummynet_filter* df = u_calloc(1, sizeof(*df));
if (!df) return NULL;
df->ua = ua;
return df;
}
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille) {
if (!df) return;
if (loss_permille > 1000) loss_permille = 1000;
df->loss_permille = loss_permille;
}
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms) {
if (!df) return;
df->delay_ms = delay_ms;
df->jitter_ms = jitter_ms;
}
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link) {
if (!df || !link) return;
dummynet_filter_detach(df);
df->link = link;
link->send_hook = dummynet_filter_send_hook;
link->send_hook_ctx = df;
}
void dummynet_filter_detach(struct dummynet_filter* df) {
if (!df || !df->link) return;
df->link->send_hook = NULL;
df->link->send_hook_ctx = NULL;
df->link = NULL;
}
void dummynet_filter_destroy(struct dummynet_filter* df) {
if (!df) return;
dummynet_filter_detach(df);
u_free(df);
}
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
return df ? &df->stats : NULL;
}
+14
View File
@@ -163,4 +163,18 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
uint64_t *delay_set, uint64_t *delay_fire,
uint64_t *shaper_set, uint64_t *shaper_fire);
// === Inline filter: встраивается в etcp_udp_send через link->send_hook ===
// Не создаёт свой сокет — перехватывает пакеты напрямую из пути отправки.
struct ETCP_LINK;
struct dummynet_filter;
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua);
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille);
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms);
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link);
void dummynet_filter_detach(struct dummynet_filter* df);
void dummynet_filter_destroy(struct dummynet_filter* df);
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df);
#endif // DUMMYNET_H
+306
View File
@@ -0,0 +1,306 @@
#include "eim_nat.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <string.h>
#include <stdlib.h>
// IP header offsets
#define IP_IHL_OFFSET 0
#define IP_PROTO_OFFSET 9
#define IP_CHECKSUM_OFFSET 10
#define IP_SRC_ADDR_OFFSET 12
#define IP_DST_ADDR_OFFSET 16
#define IP_HDR_MIN_SIZE 20
#define IP_FRAG_MF_MASK 0x2000
#define IP_FRAG_OFF_MASK 0x1FFF
#define TCP_SRC_PORT_OFFSET 0
#define TCP_DST_PORT_OFFSET 2
#define TCP_CHECKSUM_OFFSET 16
#define UDP_SRC_PORT_OFFSET 0
#define UDP_DST_PORT_OFFSET 2
#define UDP_CHECKSUM_OFFSET 6
#define ICMP_TYPE_OFFSET 0
#define ICMP_ID_OFFSET 4
#define ICMP_CHECKSUM_OFFSET 2
#define ICMP_ECHO_REQUEST 8
#define ICMP_ECHO_REPLY 0
static ip_str_t ip_host_to_str(uint32_t ip_host) {
struct in_addr a; a.s_addr = htonl(ip_host);
return ip_to_str(&a, AF_INET);
}
static uint16_t csum_update_n(uint16_t old_csum, const uint16_t* old_vals,
const uint16_t* new_vals, int n) {
uint32_t sum = (uint32_t)(uint16_t)(~old_csum);
for (int i = 0; i < n; i++) { sum -= old_vals[i]; sum += new_vals[i]; }
sum = (sum & 0xFFFF) + (sum >> 16); sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)(~sum);
}
static void csum_update_ip(uint8_t* ip_data, uint32_t old_ip_host, uint32_t new_ip_host) {
uint16_t old_csum; memcpy(&old_csum, ip_data + IP_CHECKSUM_OFFSET, 2);
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
uint16_t old_w[2] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16)};
uint16_t new_w[2] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16)};
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 2);
memcpy(ip_data + IP_CHECKSUM_OFFSET, &new_csum, 2);
}
static void csum_update_transport(uint8_t* transport, int csum_off,
uint32_t old_ip_host, uint32_t new_ip_host,
uint16_t old_port_net, uint16_t new_port_net) {
uint16_t old_csum; memcpy(&old_csum, transport + csum_off, 2);
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
uint16_t old_w[3] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16), old_port_net};
uint16_t new_w[3] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16), new_port_net};
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 3);
memcpy(transport + csum_off, &new_csum, 2);
}
static void csum_update_icmp(uint8_t* icmp, uint16_t old_word, uint16_t new_word) {
uint16_t old_csum; memcpy(&old_csum, icmp + ICMP_CHECKSUM_OFFSET, 2);
uint16_t new_csum = csum_update_n(old_csum, &old_word, &new_word, 1);
memcpy(icmp + ICMP_CHECKSUM_OFFSET, &new_csum, 2);
}
static struct eim_nat_entry* eim_nat_find_egress(struct eim_nat_ctx* ctx,
uint8_t proto, uint32_t ip_host, uint16_t port_net) {
for (uint16_t i = ctx->port_start; i <= ctx->port_end; i++) {
struct eim_nat_entry* e = &ctx->table[i];
if (e->state != EIM_NAT_ENTRY_FREE && e->proto == proto &&
e->internal_ip == ip_host && e->internal_port == port_net) return e;
}
return NULL;
}
static uint16_t eim_nat_alloc_port(struct eim_nat_ctx* ctx) {
uint16_t start = ctx->next_port;
do {
if (ctx->table[ctx->next_port].state == EIM_NAT_ENTRY_FREE) {
uint16_t port = ctx->next_port;
ctx->next_port++;
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
return port;
}
ctx->next_port++;
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
} while (ctx->next_port != start);
return 0;
}
// ==================== Public API ====================
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
uint64_t src_node_id, struct ETCP_CONN* src_conn) {
if (ip_len < IP_HDR_MIN_SIZE) return -1;
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
if (ihl < 5) return -1;
uint16_t ip_hdr_len = ihl * 4;
if (ip_len < ip_hdr_len) return -1;
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
uint8_t proto = ip_data[IP_PROTO_OFFSET];
uint32_t src_ip_net, src_ip_host;
memcpy(&src_ip_net, ip_data + IP_SRC_ADDR_OFFSET, 4);
src_ip_host = ntohl(src_ip_net);
uint8_t* transport = ip_data + ip_hdr_len;
size_t tlen = ip_len - ip_hdr_len;
uint16_t src_port_net = 0;
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
int is_icmp_echo = 0;
if (is_tcp) memcpy(&src_port_net, transport + TCP_SRC_PORT_OFFSET, 2);
else if (is_udp) memcpy(&src_port_net, transport + UDP_SRC_PORT_OFFSET, 2);
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
is_icmp_echo = 1; memcpy(&src_port_net, transport + ICMP_ID_OFFSET, 2);
}
}
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net);
uint16_t ext_port_host;
if (entry) {
ext_port_host = (uint16_t)(entry - ctx->table);
} else {
ext_port_host = eim_nat_alloc_port(ctx);
if (ext_port_host == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT port range exhausted (%s:%u proto=%u)",
ip_host_to_str(src_ip_host).str, ntohs(src_port_net), proto);
return -1;
}
entry = &ctx->table[ext_port_host];
entry->internal_ip = src_ip_host;
entry->internal_port = src_port_net;
entry->proto = proto;
entry->state = EIM_NAT_ENTRY_ACTIVE;
entry->src_node_id = src_node_id;
entry->src_conn = src_conn;
entry->last_seen = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT map: %s:%u -> %s:%u (proto=%u) from node %016llx",
ip_host_to_str(src_ip_host).str, ntohs(src_port_net),
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
(unsigned long long)src_node_id);
}
uint32_t gw_ip_net = htonl(ctx->gateway_ip);
memcpy(ip_data + IP_SRC_ADDR_OFFSET, &gw_ip_net, 4);
csum_update_ip(ip_data, src_ip_host, ctx->gateway_ip);
uint16_t new_port_net = htons(ext_port_host);
if (is_tcp) {
memcpy(transport + TCP_SRC_PORT_OFFSET, &new_port_net, 2);
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
} else if (is_udp) {
memcpy(transport + UDP_SRC_PORT_OFFSET, &new_port_net, 2);
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
} else if (is_icmp_echo) {
memcpy(transport + ICMP_ID_OFFSET, &new_port_net, 2);
csum_update_icmp(transport, src_port_net, new_port_net);
}
return 0;
}
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
struct eim_nat_entry** out_entry) {
if (ip_len < IP_HDR_MIN_SIZE) return -1;
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
if (ihl < 5) return -1;
uint16_t ip_hdr_len = ihl * 4;
if (ip_len < ip_hdr_len) return -1;
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
uint8_t proto = ip_data[IP_PROTO_OFFSET];
uint32_t dst_ip_net;
memcpy(&dst_ip_net, ip_data + IP_DST_ADDR_OFFSET, 4);
if (dst_ip_net != htonl(ctx->gateway_ip)) return 0;
uint8_t* transport = ip_data + ip_hdr_len;
size_t tlen = ip_len - ip_hdr_len;
uint16_t dst_port_net = 0;
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
int is_icmp_echo = 0;
if (is_tcp) memcpy(&dst_port_net, transport + TCP_DST_PORT_OFFSET, 2);
else if (is_udp) memcpy(&dst_port_net, transport + UDP_DST_PORT_OFFSET, 2);
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
is_icmp_echo = 1; memcpy(&dst_port_net, transport + ICMP_ID_OFFSET, 2);
}
}
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
uint16_t ext_port_host = ntohs(dst_port_net);
if (ext_port_host < ctx->port_start || ext_port_host > ctx->port_end) return 0;
struct eim_nat_entry* entry = &ctx->table[ext_port_host];
if (entry->state == EIM_NAT_ENTRY_FREE) return 0;
if (entry->proto != proto) return 0;
uint32_t dst_ip_host = ntohl(dst_ip_net);
uint32_t internal_ip_net = htonl(entry->internal_ip);
memcpy(ip_data + IP_DST_ADDR_OFFSET, &internal_ip_net, 4);
csum_update_ip(ip_data, dst_ip_host, entry->internal_ip);
uint16_t internal_port_net = entry->internal_port;
if (is_tcp) {
memcpy(transport + TCP_DST_PORT_OFFSET, &internal_port_net, 2);
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
} else if (is_udp) {
memcpy(transport + UDP_DST_PORT_OFFSET, &internal_port_net, 2);
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
} else if (is_icmp_echo) {
memcpy(transport + ICMP_ID_OFFSET, &internal_port_net, 2);
csum_update_icmp(transport, dst_port_net, internal_port_net);
}
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT unmap: %s:%u <- %s:%u (proto=%u) back to node %016llx",
ip_host_to_str(entry->internal_ip).str, ntohs(entry->internal_port),
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
(unsigned long long)entry->src_node_id);
if (out_entry) *out_entry = entry;
return 0;
}
// ==================== Init / Destroy ====================
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g) {
if (!ctx || !g) return -1;
memset(ctx, 0, sizeof(*ctx));
if (!g->nat_enabled) return 0;
if (g->nat_tun_ip.family == AF_INET) {
ctx->gateway_ip = ntohl(g->nat_tun_ip.addr.v4.s_addr);
} else if (g->tun_ip.family == AF_INET) {
ctx->gateway_ip = ntohl(g->tun_ip.addr.v4.s_addr);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No valid IP for NAT gateway");
return -1;
}
ctx->port_start = g->nat_port_start;
ctx->port_end = g->nat_port_end;
if (ctx->port_start == 0 || ctx->port_end == 0 || ctx->port_start >= ctx->port_end) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Invalid NAT port range: %u-%u", ctx->port_start, ctx->port_end);
return -1;
}
ctx->next_port = ctx->port_start;
ctx->table_size = EIM_NAT_TABLE_SIZE;
ctx->table = u_calloc(ctx->table_size, sizeof(struct eim_nat_entry));
if (!ctx->table) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to allocate NAT table"); return -1; }
for (int i = 0; i < g->nat_forward_count; i++) {
uint16_t ext = g->nat_forwards[i].external_port;
if (ext >= ctx->table_size) continue;
struct eim_nat_entry* e = &ctx->table[ext];
e->internal_ip = g->nat_forwards[i].internal_ip_host;
e->internal_port = g->nat_forwards[i].internal_port_net;
e->proto = g->nat_forwards[i].proto;
e->state = EIM_NAT_ENTRY_STATIC;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
ip_host_to_str(e->internal_ip).str, ntohs(e->internal_port), ext, e->proto);
}
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine initialized: gw=%s ports=%u-%u",
ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
return 0;
}
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx) {
if (!ctx || !ctx->initialized) return;
u_free(ctx->table);
memset(ctx, 0, sizeof(*ctx));
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine destroyed");
}
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
uint32_t internal_ip_host, uint16_t internal_port_net,
uint16_t external_port) {
if (!ctx || !ctx->initialized || external_port >= ctx->table_size) return -1;
if (ctx->table[external_port].state != EIM_NAT_ENTRY_FREE) return -1;
struct eim_nat_entry* e = &ctx->table[external_port];
e->internal_ip = internal_ip_host;
e->internal_port = internal_port_net;
e->proto = proto;
e->state = EIM_NAT_ENTRY_STATIC;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
ip_host_to_str(internal_ip_host).str, ntohs(internal_port_net), external_port, proto);
return 0;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef EIM_NAT_H
#define EIM_NAT_H
#include <stdint.h>
#include <stddef.h>
struct ETCP_CONN;
#define EIM_NAT_TABLE_SIZE 65536
#define EIM_NAT_ENTRY_FREE 0
#define EIM_NAT_ENTRY_ACTIVE 1
#define EIM_NAT_ENTRY_STATIC 2
#define IPPROTO_TCP_UINT8 6
#define IPPROTO_UDP_UINT8 17
#define IPPROTO_ICMP_UINT8 1
struct eim_nat_entry {
uint32_t internal_ip;
uint16_t internal_port;
uint8_t proto;
uint8_t state;
uint64_t src_node_id; // which node sent the original packet
uint64_t last_seen;
struct ETCP_CONN* src_conn; // cached connection for sendback
};
// Pure NAT engine state (no transport/TUN/ETCP fields)
struct eim_nat_ctx {
uint32_t gateway_ip;
uint16_t port_start;
uint16_t port_end;
uint16_t next_port;
struct eim_nat_entry* table;
size_t table_size;
int initialized;
};
struct global_config;
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g);
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx);
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
uint64_t src_node_id, struct ETCP_CONN* src_conn);
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
struct eim_nat_entry** out_entry);
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
uint32_t internal_ip_host, uint16_t internal_port_net,
uint16_t external_port);
#endif
+553 -145
View File
File diff suppressed because it is too large. Load diff
+38 -3
View File
@@ -28,15 +28,38 @@ uint16_t get_current_timestamp(void);
#define ETCP_SECTION_PAYLOAD 0x00 // Data payload
#define ETCP_SECTION_ACK 0x01 // ACK section
#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed)
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
// Inflight packet states
// Burst measurement constants
#define BURST_PACKET_COUNT 12 // число пакетов в burst
#define BURST_SKIP_COUNT 3 // сколько первых пакетов пропустить при замере gap
#define MIN_BURST_INTERVAL_TB 5000 // минимальный интервал между burst (500ms в 0.1ms)
#define BURST_RESP_TIMEOUT_TB 20000 // таймаут ожидания ответа на burst (2s в 0.1ms)
// Inflight phases
#define INFLIGHT_PHASE_SLOW_START 0
#define INFLIGHT_PHASE_CONG_AVOIDANCE 1
// Section sizes
#define MEAS_TS_SECTION_SIZE 9 // type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2)
#define MEAS_RESP_SECTION_SIZE 13 // type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1)
#define FILLER_HDR_SIZE 3 // type(1) + len(2)
// MEAS_TS flags
#define MEAS_FLAG_IS_FILLER 0x01 // пакет содержит FILLER вместо PAYLOAD
#define MEAS_FLAG_IS_LAST 0x02 // последний пакет в burst
#define MEAS_FLAG_IS_FIRST 0x04 // первый пакет в burst
// MEAS_RESP valid
#define MEAS_RESP_VALID 1
#define INFLIGHT_STATE_WAIT_ACK 0
#define INFLIGHT_STATE_WAIT_SEND 1
#define INFLIGHT_INITIAL_HASH_SIZE 1024
#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак)
#define ASM_BUF_MAX_SIZE (64 * 1024) // максимальный размер буфера сборки фрагментов
// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool.
// пакет полностью удаляется когда приходит ACK (либо conn_reset/close)
@@ -49,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 (используем быстрый поиск с хешем)
@@ -77,6 +103,7 @@ struct ETCP_CONN {
// Links (channels) - linked list
struct ETCP_LINK* links;
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
// Crypto and state
struct secure_channel crypto_ctx;
@@ -149,6 +176,7 @@ struct ETCP_CONN {
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
@@ -164,6 +192,8 @@ struct ETCP_CONN {
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
uint32_t cnt_ack_miss; // счетчик не найденных ack
uint32_t cnt_link_wait; // счетчик переходов в ожидание когда link busy
uint32_t rx_dup_count; // счетчик принятых дубликатов (локальные)
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
uint32_t debug[8]; // 8 значений для дебага (live watch)
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
@@ -200,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);
@@ -208,6 +241,8 @@ void etcp_conn_ready(struct ETCP_CONN* conn);
void etcp_on_link_down(struct ETCP_CONN* etcp);
void etcp_update_mtu(struct ETCP_CONN* etcp);
#ifdef __cplusplus
}
#endif
+19 -26
View File
@@ -57,24 +57,21 @@ int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
return -1;
}
if (id >= ETCP_MAX_BINDINGS) {
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
return -1;
}
if (!callback) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
return -1;
}
if (inst->api_bindings.callbacks[id] != NULL) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: ID %u already bound, overwriting", id);
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
}
inst->api_bindings.callbacks[id] = callback;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound ID %u to callback %p for instance %p",
id, (void*)callback, (void*)inst);
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
return 0;
}
@@ -83,8 +80,8 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
return -1;
}
if (id >= ETCP_MAX_BINDINGS) {
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
return -1;
}
@@ -101,40 +98,36 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
}
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
return -1;
}
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
return -1;
}
if (!conn->normalizer) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
return -1;
}
if (conn->initialized==0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
return -1;
}
struct PKTNORM* pn = conn->normalizer;
if (!pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Normalizer has no input queue");
if (!pn || !pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
return -1;
}
// Помещаем entry в очередь input normalizer
// queue_data_put забирает ownership entry
// DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "Before put to input");
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, "After put to input");
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
if (result != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed (queue full?)");
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: Packet queued for sending to [%s]", conn->log_name);
return 0;
}
@@ -181,7 +174,7 @@ void etcp_int_recv(struct ll_queue* queue, void* arg) {
}
// Ищем binding для этого ID в instance
if (id < ETCP_MAX_BINDINGS && inst->api_bindings.callbacks[id] != NULL) {
if (inst->api_bindings.callbacks[id] != NULL) {
// Вызываем коллбэк
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
id, (void*)inst->api_bindings.callbacks[id]);
+6
View File
@@ -23,6 +23,12 @@
// ETCP packet IDs
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy 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);
+814 -238
View File
File diff suppressed because it is too large. Load diff
+136 -30
View File
@@ -5,20 +5,36 @@
#include "secure_channel.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h"
#include <stdint.h>
#include <stddef.h>
#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт]
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define 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 */
#define ETCP_RFC791_MIN_MTU 576
// Типы кодограмм протокола
#define ETCP_INIT_REQUEST 0x02
#define ETCP_INIT_RESPONSE 0x03
#define ETCP_INIT_REQUEST_NOINIT 0x04
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#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)
@@ -32,6 +48,26 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
};
#pragma pack(pop)
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
struct PING_CONTEXT* next;
struct UTUN_INSTANCE* instance;
etcp_ping_callback_t cb;
void* arg;
uint64_t nonce;
void* timeout_timer;
uint64_t send_time; // время отправки пинга в 0.1ms
uint8_t* user_data;
size_t user_data_len;
};
// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений
struct ETCP_SOCKET {
struct ETCP_SOCKET* next; // Linked list для всех соединений
@@ -40,7 +76,6 @@ struct ETCP_SOCKET {
socket_t fd; // UDP socket (cross-platform)
struct sockaddr_storage local_addr; // Локальный адрес
int mtu; // MTU для этого сокета
int loss_rate; // packet loss rate in percent (0-100)
// для входящих подключений (links) - массив упорядоченный по ip_port_hash
size_t max_channels; // сколько выделено памяти
@@ -51,9 +86,43 @@ struct ETCP_SOCKET {
void* socket_id; // Socket ID from uasync_add_socket
uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
uint8_t sock_id; // unique socket id (0-255) for NAT matching
uint8_t nat_type; // NAT_TYPE_* (detected by server)
uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order)
uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers
uint8_t only_local; // 1 = only local connections, no forwarding
struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига
struct sockaddr_storage nat_addr; // NAT адрес (network byte order), ss_family=0 = не определён
};
// NAT check status
#define NAT_CHECK_NONE 0
#define NAT_CHECK_WAITING 1
#define NAT_CHECK_IN_PROGRESS 2
#define NAT_CHECK_EIM 3
#define NAT_CHECK_STRICT 4
// NAT type (detected by server during NAT check)
#define NAT_TYPE_UNKNOWN 0
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
// Verified NAT types (published in nodeinfo after server-side NAT detection)
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive
#define NAT_VERIFIED_EIM 5 // EIM NAT
#define NAT_VERIFIED_STRICT 6 // strict NAT
#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT
// Тип функции-перехватчика отправки UDP.
// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto.
struct ETCP_LINK;
typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* context);
// ETCP Link - одно динамическое соединение (один путь)
struct ETCP_LINK {
uint32_t ip_port_hash; // crc32 для быстрого поиска
@@ -61,7 +130,6 @@ struct ETCP_LINK {
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
@@ -75,6 +143,10 @@ struct ETCP_LINK {
uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request)
uint8_t local_link_id; // id моего линка
uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized)
uint8_t remote_socket_id; // socket id peer
uint8_t remote_only_local; // only_local flag from peer
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
@@ -100,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;
@@ -129,13 +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)
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
@@ -145,20 +195,61 @@ struct ETCP_LINK {
// NAT address tracking (from INIT_RESPONSE)
uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set
uint16_t nat_port; // NAT port (network byte order)
uint16_t nat_port; // NAT port (host byte order)
uint32_t nat_changes_count; // Counter of NAT address changes
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
uint8_t nat_check_status; // NAT_CHECK_*
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
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
uint16_t handshake_minsize; // минимальный размер udp при handshake
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
// === Burst-измерение bandwidth ===
// Sender-сторона
uint16_t burst_id; // монотонно возрастающий ID burst
uint8_t burst_active; // 1 = burst в процессе отправки
uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1)
uint8_t burst_count; // общее число пакетов в burst
uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL
void* burst_resp_timer; // таймер ожидания ответа на burst
uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты)
uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW
// Receiver-сторона
uint16_t burst_recv_id; // ID отслеживаемого burst
uint8_t burst_recv_count; // ожидаемое число пакетов в burst
uint8_t burst_recv_next_seq; // следующий ожидаемый seq
uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен
uint8_t burst_recv_received; // сколько пакетов уже принято
uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs)
uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа)
uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback
uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs)
uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs)
uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения
// === 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;
};
// Единая точка отправки всех ETCP-пакетов.
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len);
// INITIALIZATION
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
int init_sockets(struct UTUN_INSTANCE* instance);
@@ -174,19 +265,34 @@ 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);
void etcp_link_burst_check(struct ETCP_LINK* link);
void etcp_link_burst_finish(struct ETCP_LINK* link);
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback.
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr,
int timeout_ms, etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
#endif // ETCP_CONNECTIONS_H
+4 -3
View File
@@ -42,11 +42,12 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
case ETCP_SECTION_ACK: {
if (offset + 2 > len) { offset = len; break; }
ack_count = data[offset + 1];
if (offset + 6 > len) { offset = len; break; }
if (offset + 8 > len) { offset = len; break; }
ack_last_delivered = data[offset + 2] | (data[offset + 3] << 8) |
(data[offset + 4] << 16) | (data[offset + 5] << 24);
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d", ack_last_delivered, ack_count);
offset += 6 + ack_count * 8;
uint16_t ack_rx_dup = data[offset + 6] | (data[offset + 7] << 8);
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d dup=%u", ack_last_delivered, ack_count, ack_rx_dup);
offset += 8 + ack_count * 8;
break;
}
case ETCP_SECTION_TIMESTAMP: {
+247
View File
@@ -0,0 +1,247 @@
#include "etcp_dump.h"
#include "etcp.h"
#include "pkt_normalizer.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/socket_compat.h"
#include <stdio.h>
#include <string.h>
#define DUMP_FP stdout
#define DUMP_LINE(fmt, ...) do { \
fprintf(DUMP_FP, " " fmt "\n", ##__VA_ARGS__); \
} while(0)
#define DUMP_HDR(fmt, ...) do { \
fprintf(DUMP_FP, fmt "\n", ##__VA_ARGS__); \
} while(0)
static void dump_queue(const char* name, struct ll_queue* q) {
if (!q) { DUMP_LINE("%-14s NULL", name); return; }
DUMP_LINE("%-14s %d pkts / %zu bytes", name, queue_entry_count(q), queue_total_bytes(q));
}
static void dump_pool(const char* name, struct memory_pool* pool) {
if (!pool) return;
size_t allocs, reuse;
memory_pool_get_stats(pool, &allocs, &reuse);
DUMP_LINE(" %-12s alloc=%zu reuse=%zu", name, allocs, reuse);
}
static void dump_link_timer(const char* name, void* timer) {
DUMP_LINE(" %-14s %s", name, timer ? "ACTIVE" : "free");
}
static const char* ip_to_str_buf(uint32_t ip, char* buf, size_t sz) {
uint8_t* b = (uint8_t*)&ip;
snprintf(buf, sz, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
return buf;
}
static const char* sockaddr_to_str(const struct sockaddr_storage* addr, char* buf, size_t sz) {
if (!addr || addr->ss_family != AF_INET) { snprintf(buf, sz, "?"); return buf; }
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
uint8_t* b = (uint8_t*)&sin->sin_addr.s_addr;
snprintf(buf, sz, "%u.%u.%u.%u:%u", b[0], b[1], b[2], b[3], ntohs(sin->sin_port));
return buf;
}
void etcp_dump_conn_state(struct ETCP_CONN* conn) {
if (!conn) { DUMP_HDR("etcp_dump_conn_state: NULL conn"); return; }
char addr_buf[32], nat_buf[32];
DUMP_HDR("=== ETCP CONN STATE [%s] ===", conn->log_name);
/* --- GENERAL --- */
DUMP_LINE("GENERAL: %s peer=0x%llx initialized=%d links_up=%d tx_state=%d session=0x%08x mtu=%d routing_ex=%d",
conn->name ? conn->name : "",
(unsigned long long)conn->peer_node_id, conn->initialized, conn->links_up,
conn->tx_state, conn->session_id, conn->mtu, conn->routing_exchange_active);
/* --- IDS --- */
DUMP_LINE("IDS: next_tx=%u last_rx=%u last_del=%u rx_ack_till=%u",
conn->next_tx_id, conn->last_rx_id, conn->last_delivered_id, conn->rx_ack_till);
/* --- RTT --- */
DUMP_LINE("RTT: last=%u avg10=%u avg100=%u jitter=%u",
conn->rtt_last, conn->rtt_avg_10, conn->rtt_avg_100, conn->jitter);
/* --- STATS --- */
DUMP_LINE("STATS: bytes_sent=%u retrans=%u reinit=%u reset=%u ack_pkts=%u",
conn->bytes_sent_total, conn->retransmissions_count,
conn->reinit_count, conn->reset_count, conn->ack_packets_count);
/* --- INFLIGHT --- */
DUMP_LINE("INFLIGHT: unacked=%u optimal=%u",
conn->unacked_bytes, conn->optimal_inflight);
/* --- ACK DEBUG --- */
DUMP_LINE("ACK_DEBUG: hit_inf=%u hit_sndq=%u miss=%u link_wait=%u rx_dup=%u tx_dup=%u",
conn->cnt_ack_hit_inf, conn->cnt_ack_hit_sndq, conn->cnt_ack_miss,
conn->cnt_link_wait, conn->rx_dup_count, conn->tx_dup_count);
/* --- DEBUG[8] --- */
DUMP_LINE("DEBUG: [0]=%u [1]=%u [2]=%u [3]=%u [4]=%u [5]=%u [6]=%u [7]=%u",
conn->debug[0], conn->debug[1], conn->debug[2], conn->debug[3],
conn->debug[4], conn->debug[5], conn->debug[6], conn->debug[7]);
/* --- TIMERS --- */
DUMP_LINE("TIMERS: retrans=%s ack_resp=%s",
conn->retrans_timer ? "ACTIVE" : "free",
conn->ack_resp_timer ? "ACTIVE" : "free");
/* --- QUEUES --- */
DUMP_HDR(" --- QUEUES ---");
dump_queue("input", conn->input_queue);
dump_queue("input_send_q", conn->input_send_q);
dump_queue("input_wait_ack", conn->input_wait_ack);
dump_queue("ack_q", conn->ack_q);
dump_queue("recv_q", conn->recv_q);
dump_queue("output", conn->output_queue);
/* --- NORMALIZER --- */
if (conn->normalizer) {
struct PKTNORM* pn = (struct PKTNORM*)conn->normalizer;
DUMP_HDR(" --- NORMALIZER ---");
DUMP_LINE("frag=%u data_ptr=%u/%u flush=%s pending=%s recvpart=%s",
pn->frag_size, pn->data_ptr, pn->data_size,
pn->flush_timer ? "ACTIVE" : "free",
pn->pending ? "ACTIVE" : "free",
pn->recvpart ? "ACTIVE" : "free");
dump_queue("pn-input", pn->input);
dump_queue("pn-output", pn->output);
DUMP_LINE("alloc_err=%u logic_err=%u in=%llu/%llu out=%llu/%llu",
pn->alloc_errors, pn->logic_errors,
(unsigned long long)pn->in_total_pkts, (unsigned long long)pn->in_total_bytes,
(unsigned long long)pn->out_total_pkts, (unsigned long long)pn->out_total_bytes);
} else {
DUMP_HDR(" --- NORMALIZER --- NULL");
}
/* --- POOLS --- */
DUMP_HDR(" --- POOLS ---");
dump_pool("inflight", conn->inflight_pool);
dump_pool("io", conn->io_pool);
if (conn->instance) {
dump_pool("data", conn->instance->data_pool);
dump_pool("ack", conn->instance->ack_pool);
dump_pool("pkt", conn->instance->pkt_pool);
}
/* --- LINKS --- */
int link_idx = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
DUMP_HDR(" --- LINK %d ---", link_idx);
sockaddr_to_str(&link->remote_addr, addr_buf, sizeof(addr_buf));
/* basic link state */
DUMP_LINE("BASIC: id=%d/%d is_server=%d link_state=%d link_status=%d initialized=%d mtu=%d",
link->local_link_id, link->remote_link_id, link->is_server,
link->link_state, link->link_status, link->initialized, link->mtu);
DUMP_LINE("ADDR: remote=%s remote_sock=%d remote_type=%d remote_only_local=%d",
addr_buf, link->remote_socket_id, link->remote_type, link->remote_only_local);
/* keepalive */
DUMP_LINE("KA: recv=%d remote=%d sent=%u recv=%u interval=%u timeout=%u",
link->recv_keepalive, link->remote_keepalive,
link->keepalive_sent_count, link->keepalive_recv_count,
link->keepalive_interval, link->keepalive_timeout);
/* timers */
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s 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->burst_resp_timer ? "ACTIVE" : "free",
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* 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->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
link->bbr_pacing_rate);
/* 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",
link->tt_last, link->rt_last,
link->recv_dt_avg_tx, link->recv_dt_avg_rx,
link->bandwidth);
/* errors */
DUMP_LINE("MTU: %u local=%u remote=%u", link->mtu, link->mtu_local, link->mtu_remote);
DUMP_LINE("ERRORS: enc=%zu dec=%zu snd=%zu rcv=%zu total_enc=%zu total_dec=%zu retrans=%u",
link->encrypt_errors, link->decrypt_errors,
link->send_errors, link->recv_errors,
link->total_encrypted, link->total_decrypted,
link->total_retransmissions);
/* nat */
ip_to_str_buf(link->nat_ip, nat_buf, sizeof(nat_buf));
DUMP_LINE("NAT: ip=%s:%u chg=%u hits=%u check=%d type=%d",
nat_buf, link->nat_port,
link->nat_changes_count, link->nat_hits_count,
link->nat_check_status, link->nat_type);
/* last recv */
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
(unsigned long long)link->last_recv_local_time,
link->last_recv_timestamp, link->last_recv_updated);
/* handshake */
DUMP_LINE("HANDSHAKE: min=%u max=%u", link->handshake_minsize, link->handshake_maxsize);
/* burst */
DUMP_LINE("BURST: active=%d id=%u seq=%u/%u last_tb=%llu resp=%s",
link->burst_active, link->burst_id,
link->burst_seq, link->burst_count,
(unsigned long long)link->burst_last_time_tb,
link->burst_resp_timer ? "ACTIVE" : "free");
/* shaper */
DUMP_LINE("SHAPER: load_tb=%llu sub=%llu state=%d",
(unsigned long long)link->shaper_load_time_tb,
(unsigned long long)link->shaper_sub_nanotime,
link->shaper_state);
link = link->next;
link_idx++;
}
if (link_idx == 0) DUMP_HDR(" --- NO LINKS ---");
DUMP_HDR("=== END [%s] ===", conn->log_name);
}
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance) {
if (!instance) {
DUMP_HDR("etcp_dump_all_conns: NULL instance");
return;
}
DUMP_HDR("=== DUMP ALL CONNS for instance node=0x%llx ===",
(unsigned long long)instance->node_id);
struct ETCP_CONN* conn = instance->connections;
int idx = 0;
while (conn) {
DUMP_HDR("--- CONN %d ---", idx);
etcp_dump_conn_state(conn);
conn = conn->next;
idx++;
}
if (idx == 0) DUMP_HDR("--- NO CONNECTIONS ---");
DUMP_HDR("=== END DUMP ALL ===");
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef ETCP_DUMP_H
#define ETCP_DUMP_H
#include "etcp.h"
#include "utun_instance.h"
void etcp_dump_conn_state(struct ETCP_CONN* conn);
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance);
#endif
+71 -40
View File
@@ -25,6 +25,7 @@
static void shaper_timer_cb(void* arg); // Shaper wait callback
// Select link for transmission
// Algorithm: min inflight_bytes, round-robin among ties
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
if (!etcp || !etcp->links) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] invalid parameters (etcp=%p, links=%p)",
@@ -32,19 +33,12 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
return NULL;
}
struct ETCP_LINK* best = NULL;
uint64_t min_load_tb = UINT64_MAX;
uint64_t now_tb=get_time_tb();
// First pass: find minimum inflight_bytes among available links
uint32_t min_inflight = UINT32_MAX;
int available_count = 0;
struct ETCP_LINK* link = etcp->links;
int link_index = 0;
while (link) {
link_index++;
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu",
// link_index, link, link->initialized, link->shaper_timer==NULL?1:0,
// (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime);
if (!link->initialized || link->link_status != 1) {
link = link->next;
continue;
@@ -54,40 +48,73 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
continue;
}
if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA;
// Check if ready (load < now + burst allowance)
uint64_t effective_load_ns = link->shaper_load_time_tb * TIMEBASE_NS + (link->shaper_sub_nanotime / 10); // To ns
uint64_t now_ns = now_tb * TIMEBASE_NS;
int64_t delta_t=(int64_t)(now_ns - effective_load_ns);// uint64 может переполняться. чтобы обеспечить правильную цикличность.
if (link->shaper_load_time_tb < min_load_tb || (link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) {
min_load_tb = link->shaper_load_time_tb;
best = link;
available_count++;
if (link->inflight_bytes < min_inflight) {
min_inflight = link->inflight_bytes;
}
link = link->next;
}
if (best) {
// DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] selected link %p (load_tb=%llu)",
// etcp->log_name, best, (unsigned long long)min_load_tb);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found", etcp->log_name);
if (available_count == 0) {
if (etcp->links)
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: inf:%s, tmr:%s",
etcp->log_name, etcp->links->send_blocked_inflight?"wait":"rdy", etcp->links->shaper_timer?"wait":"rdy");
return NULL;
}
return best;
// Count how many links have the minimum inflight
int min_count = 0;
link = etcp->links;
while (link) {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
min_count++;
}
link = link->next;
}
if (min_count == 1) {
// Only one link with minimum inflight - return it directly
link = etcp->links;
while (link) {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
return link;
}
link = link->next;
}
}
// Round-robin among links with minimum inflight
// Start from the link after last_rr_link (or from the beginning if last_rr_link is NULL)
struct ETCP_LINK* start = etcp->last_rr_link ? etcp->last_rr_link->next : etcp->links;
if (!start) start = etcp->links;
link = start;
do {
if (link->initialized && link->link_status == 1 &&
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
etcp->last_rr_link = link;
return link;
}
link = link->next;
if (!link) link = etcp->links;
} while (link != start);
// Should not reach here, but return NULL just in case
return NULL;
}
// New: Send dgram (select link, encrypt/send, update shaper)
void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
if (!dgram) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: called with NULL dgram");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "called with NULL dgram");
return;
}
struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL;
if (!etcp) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] no ETCP_CONN associated with dgram", etcp->log_name);
memory_pool_free(etcp->instance->pkt_pool, dgram);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no ETCP_CONN associated with dgram (link=%p)", (void*)dgram->link);
return;
}
@@ -114,12 +141,14 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
}
// Update shaper after successful send
if (link->bandwidth == 0) {
if (link->burst_active) {
// burst bypasses shaper — не обновляем нагрузку
} else if (link->bandwidth == 0) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] unlimited bandwidth, skipping shaper update", etcp->log_name);
// Don't return here, let the function continue to free dgram at the end
} else {
// Time to transmit (ns per byte = 8e9 / bw for bits/sec)
double byte_time_ns = 8000000000.0 / (double)link->bandwidth;
// Time to transmit (ns per byte = 8e6 / bw for Kbits/sec)
double byte_time_ns = 8000000.0 / (double)link->bandwidth;
uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns + 0.5); // Round nearest
// To sub_nanotime (*10 for 0.1ns)
@@ -135,8 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
uint64_t now_tb=get_time_tb();
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu)", etcp->log_name, (unsigned long long)wait_tb);
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");
}
// Inactivity correction
@@ -150,6 +180,7 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
if (!link) return 0;
if (link->burst_active) return 1; // burst bypasses all limits
int can = 1;
if (link->inflight_bytes >= link->inflight_lim_bytes) {
link->send_blocked_inflight = 1;
@@ -168,25 +199,25 @@ int loadbalancer_link_can_send(struct ETCP_LINK* link) {
void loadbalancer_link_ready(struct ETCP_LINK* link) {
if (!link || !link->etcp) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: invalid link (%p)", link);
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
return;
}
if (loadbalancer_link_can_send(link) == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link still blocked");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link=%p now ready, notifying ETCP_CONN", link);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
if (link->etcp->link_ready_for_send_fn) {
link->etcp->link_ready_for_send_fn(link->etcp);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: no link_ready_for_send_fn set");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "no link_ready_for_send_fn set");
}
}
// Get ETCP link status: 1 = at least one link is up, 0 = all links down or no links
int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
if (!etcp) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_get_link_status: NULL etcp");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "NULL etcp");
return 0;
}
@@ -201,7 +232,7 @@ int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] link status check: %d alive links",
etcp->log_name ? etcp->log_name : "????→????", alive_count);
etcp->log_name[0] ? etcp->log_name : "????→????", alive_count);
return (alive_count > 0) ? 1 : 0;
}
@@ -212,7 +243,7 @@ static void shaper_timer_cb(void* arg) {
if (!link) return;
link->shaper_timer = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "shaper_timer_cb: link=%p now ready", link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link=%p now ready", link);
// Notify loadbalancer that link is ready
loadbalancer_link_ready(link);
+747
View File
@@ -0,0 +1,747 @@
// etcp_router.c — Сервисный слой маршрутизации поверх ETCP
// Упрощённый TCP: восстановление порядка (recv_q), дедупликация, без переповторов
// ACK: периодическая отправка rx_seq (не чаще 100ms), idle-таймер для последнего seq
// Inflight контроль через send_q: при переполнении — очередь + retry-таймер, без дропов
#include "etcp_router.h"
#include "etcp.h"
#include "utun_instance.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
#include <string.h>
// ====================================================================
// Внутренние forward declarations
// ====================================================================
static void router_ack_timer_cb(void* arg);
static void router_idle_ack_timer_cb(void* arg);
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags);
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
static void router_send_resume_cb(void* arg);
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn);
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn);
// ====================================================================
// Управление ROUTER_CONN
// ====================================================================
static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id) {
if (!inst || !inst->router_conns) return NULL;
uint8_t key[9];
memcpy(key, &remote_node_id, 8);
key[8] = svc_id;
return (struct ETCP_ROUTER_CONN*)queue_find_data_by_index(inst->router_conns, key);
}
// ====================================================================
// Отправка: router_send_one + send_q
// ====================================================================
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
return router_send_one_flags(rconn, payload, pl_len, 0);
}
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
struct UTUN_INSTANCE* inst = rconn->inst;
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
? 0 : rconn->tx_seq++;
seq |= seq_flags;
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len;
uint8_t* dgram = u_malloc(total_len);
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = seq;
hdr->svc_id = rconn->svc_id;
{
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);
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
entry->dgram = dgram;
entry->len = total_len;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_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_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);
}
// Отправка без conn (RST для неизвестного src)
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags) {
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, dst);
if (!conn) return;
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) return;
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = dst;
hdr->src_node_id = inst->node_id;
hdr->seq = seq_flags;
hdr->svc_id = svc_id;
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
entry->len = SVC_ROUTE_HDR_SIZE;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
seq_flags, (unsigned long long)dst, svc_id);
etcp_send(conn, entry);
}
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
struct UTUN_INSTANCE* inst = rconn->inst;
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
if (!cb) return;
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(9);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = rconn->svc_id;
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);
}
// Закрыть conn: отправить CLOSE удалённой стороне, уведомить локальный сервис, очистить
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
if (!rconn) return;
struct UTUN_INSTANCE* inst = rconn->inst;
router_send_close_to_service(rconn);
// Отправляем CLOSE удалённой стороне
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG);
// Таймеры
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q); rconn->recv_q = NULL;
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q); rconn->send_q = NULL;
}
if (inst->router_conns)
queue_remove_data(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_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 ((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);
queue_dgram_free(e); queue_entry_free(e);
}
if (rconn->send_blocked && !rconn->send_resume_timer)
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
if (!rconn->send_blocked && rconn->send_resume_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->send_resume_timer);
rconn->send_resume_timer = NULL;
}
}
static void router_send_resume_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->send_resume_timer = NULL;
router_drain_send_q(rconn);
}
static 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_ETCPROUTE, "router_enqueue_send: queue_entry_new failed"); return -1; }
qe->dgram = u_malloc(pl_len);
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");
return 0;
}
// ====================================================================
// ACK timers
// ====================================================================
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_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_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_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->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->last_sent_ack_seq)
router_ack_do_send(rconn);
}
// ====================================================================
// Reorder / assembly (аналог etcp_output_try_assembly)
// ====================================================================
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn,
const uint8_t* payload, size_t payload_len) {
struct UTUN_INSTANCE* inst = rconn->inst;
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
if (!cb) {
DEBUG_WARN(DEBUG_CATEGORY_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_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_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);
}
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn) {
uint32_t next = rconn->rx_seq;
while (1) {
struct ll_entry* entry = queue_find_data_by_index(rconn->recv_q, &next);
if (!entry) break;
queue_remove_data(rconn->recv_q, entry);
router_deliver(rconn, conn, entry->dgram, entry->len);
rconn->rx_seq = next + 1;
next++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
}
// ====================================================================
// etcp_router_recv_cb — обработчик ETCP_ID_SVC_ROUTE
// ====================================================================
static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry) return;
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
if (!inst || entry->len < SVC_ROUTE_HDR_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_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;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)entry->dgram;
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_RECV: svc_id=%u seq=%u len=%zu from %016llx",
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
if (hdr->dst_node_id == inst->node_id) {
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
// CLOSE или RST — pl_len==0 и флаг в seq (закрываем conn)
if (pl_len == 0 && (hdr->seq & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
if (rconn) router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (pl_len == 0) {
if (rconn) {
if ((int32_t)(hdr->seq - rconn->tx_acked) >= 0) {
rconn->tx_acked = hdr->seq;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d from %016llx",
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
queue_entry_count(rconn->send_q),
(unsigned long long)hdr->src_node_id);
} 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);
}
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Data-пакет — авто-создаём conn если ещё нет
if (!rconn) {
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
}
// Обнаружение перезапуска 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;
// Проверка границ (32-bit circular, аналог ETCP MAX_INFLIGHT_SIZE)
{
int32_t d = (int32_t)(seq - rconn->rx_seq);
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
seq, rconn->rx_seq, d);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
// Дубликат (seq уже доставлен или в recv_q)
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
queue_find_data_by_index(rconn->recv_q, &seq)) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Кладём в recv_q
struct ll_entry* qe = queue_entry_new(4);
if (!qe) { queue_dgram_free(entry); queue_entry_free(entry); return; }
*(uint32_t*)qe->data = seq;
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
qe->len = pl_len;
memcpy(qe->dgram, pl, pl_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
(unsigned long long)hdr->src_node_id, rconn->svc_id);
queue_data_put_with_index(rconn->recv_q, qe);
if (seq == rconn->rx_seq)
router_try_assembly(rconn, conn);
router_schedule_ack(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
} else {
// ========== Транзит ==========
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
if (!next) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
etcp_send(next, entry);
}
}
// ====================================================================
// Public API
// ====================================================================
int etcp_router_init(struct UTUN_INSTANCE* inst) {
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_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_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_ETCPROUTE, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
return 0;
}
void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
etcp_unbind(inst, ETCP_ID_SVC_ROUTE);
if (inst->router_conns) {
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
router_send_close_to_service(rconn);
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
queue_entry_free(entry);
}
queue_free(inst->router_conns);
inst->router_conns = NULL;
}
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
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_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_ETCPROUTE, "etcp_router_bind: overwriting svc_id=%u", svc_id);
inst->router_bindings.callbacks[svc_id] = 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_ETCPROUTE, "etcp_router_unbind: NULL instance"); return -1; }
if (!inst->router_bindings.callbacks[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_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 force) {
if (!inst || !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_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_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); }
return 0;
}
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
if (!etcp_conn) {
DEBUG_WARN(DEBUG_CATEGORY_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;
}
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 ((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 eq_ret;
}
int ret = router_send_one(rconn, entry->dgram + 1, payload_len);
queue_dgram_free(entry); queue_entry_free(entry);
return ret;
}
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
if (!inst || !inst->bgp) return -1;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return -1;
return queue_entry_count(conn->normalizer->input);
}
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
// ====================================================================
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id) {
if (!inst || !inst->router_conns) return NULL;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
if (rconn) return rconn;
size_t data_size = sizeof(struct ETCP_ROUTER_CONN) - sizeof(struct ll_entry);
rconn = (struct ETCP_ROUTER_CONN*)queue_entry_new(data_size);
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_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->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_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
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_ETCPROUTE, "router_conn_send: invalid args rconn=%p data=%p len=%zu",
(void*)rconn, (const void*)data, len);
return -1;
}
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);
}
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
router_close_and_notify(rconn);
}
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id) {
if (!inst || !inst->router_conns) return;
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
if (rconn->remote_node_id == remote_node_id) {
router_send_close_to_service(rconn);
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed by node remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
} else {
queue_data_put(inst->router_conns, entry);
}
}
}
+127
View File
@@ -0,0 +1,127 @@
// etcp_router.h — Сервисный слой маршрутизации поверх ETCP
// Маршрутизирует сервисные пакеты до целевой ноды, на промежуточных нодах ретранслирует
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
//
// Формат SVC_ROUTE пакета:
// [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
#include <stdint.h>
#include "etcp_api.h"
#pragma pack(push, 1)
struct SVC_ROUTE_HDR {
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
uint64_t dst_node_id;
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
uint8_t svc_id; // идентификатор сервиса
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
};
#pragma pack(pop)
#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 — хеш-индекс
uint64_t remote_node_id; // = data[0..7]
uint8_t svc_id; // = data[8]
uint16_t last_dgram_ts; // последний timestamp датаграммы (rx или tx, timebase 0.1ms)
uint32_t tx_seq; // следующий seq для отправки
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
uint32_t 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; // периодический таймер (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 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
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
struct ETCP_ROUTER_BINDINGS {
etcp_recv_fn callbacks[SVC_ROUTE_MAX_BINDINGS];
};
// Инициализация: etcp_bind(ETCP_ID_SVC_ROUTE) + очистка bindings + создание router_conns
int etcp_router_init(struct UTUN_INSTANCE* inst);
// Деинициализация
void etcp_router_destroy(struct UTUN_INSTANCE* inst);
// Зарегистрировать обработчик сервиса
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback);
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force);
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t remote_node_id, uint8_t svc_id);
// Отправить данные с авто-seq и контролем inflight
// data: payload без svc_id, flags: битовые флаги (зарезервировано)
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
// Закрыть все router_conn для указанного remote_node_id (peer умер)
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
// 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
+130
View File
@@ -0,0 +1,130 @@
// lwip_pbuf.c — simplified packet buffer (PBUF_RAM only)
#include "lwip_pbuf.h"
#include "../../lib/mem.h"
#include <string.h>
#define PBUF_POOL_BUFSIZE 256 // not used with PBUF_RAM only
struct pbuf* pbuf_alloc(uint16_t layer, uint16_t length)
{
uint16_t offset = (layer == PBUF_TRANSPORT) ? 54 : ((layer == PBUF_IP) ? 34 : (uint16_t)layer);
uint16_t total = sizeof(struct pbuf) + offset + length;
struct pbuf *p = u_malloc(total);
if (!p) return NULL;
p->next = NULL;
p->payload = (uint8_t*)p + sizeof(struct pbuf) + offset;
p->tot_len = length;
p->len = length;
p->ref = 1;
p->flags = 0;
p->type_internal = 0;
return p;
}
void pbuf_free(struct pbuf *p)
{
while (p) {
struct pbuf *next = p->next;
if (p->ref > 0) p->ref--;
if (p->ref == 0) u_free(p);
p = next;
}
}
void pbuf_ref(struct pbuf *p)
{
if (p) p->ref++;
}
int pbuf_header(struct pbuf *p, int16_t increment)
{
if (!p) return -1;
if (increment == 0) return 0;
if (increment < 0) {
if ((uint16_t)(-increment) > p->len) return -1;
} else {
if (p->payload < (void*)((uint8_t*)p + sizeof(struct pbuf))) return -1;
uint16_t avail = (uint16_t)((uint8_t*)p->payload - ((uint8_t*)p + sizeof(struct pbuf)));
if ((uint16_t)increment > avail) return -1;
}
p->payload = (uint8_t*)p->payload - increment;
p->len = (uint16_t)(p->len + increment);
p->tot_len = (uint16_t)(p->tot_len + increment);
return 0;
}
int pbuf_header_force(struct pbuf *p, int16_t increment)
{
if (!p) return -1;
if (increment < 0) {
if ((uint16_t)(-increment) > p->len) return -1;
}
p->payload = (uint8_t*)p->payload - increment;
p->len = (uint16_t)(p->len + increment);
p->tot_len = (uint16_t)(p->tot_len + increment);
return 0;
}
void pbuf_chain(struct pbuf *head, struct pbuf *tail)
{
if (!head) return;
struct pbuf *p = head;
while (p->next) p = p->next;
p->next = tail;
}
struct pbuf* pbuf_dechain(struct pbuf *p)
{
if (!p) return NULL;
struct pbuf *next = p->next;
p->next = NULL;
p->tot_len = p->len;
return next;
}
uint16_t pbuf_clen(const struct pbuf *p)
{
uint16_t count = 0;
while (p) { count++; p = p->next; }
return count;
}
uint16_t pbuf_copy_partial(const struct pbuf *p, void *buf, uint16_t len, uint16_t offset)
{
uint16_t copied = 0;
while (p && len > 0) {
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
uint16_t chunk = (uint16_t)(p->len - offset);
if (chunk > len) chunk = len;
memcpy((uint8_t*)buf + copied, (uint8_t*)p->payload + offset, chunk);
copied = (uint16_t)(copied + chunk); len = (uint16_t)(len - chunk); offset = 0;
p = p->next;
}
return copied;
}
int pbuf_take(struct pbuf *p, const void *data, uint16_t len)
{
return pbuf_take_at(p, data, len, 0);
}
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset)
{
uint16_t remaining = len;
const uint8_t *src = (const uint8_t*)data;
while (p && remaining > 0) {
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
uint16_t chunk = (uint16_t)(p->len - offset);
if (chunk > remaining) chunk = remaining;
memcpy((uint8_t*)p->payload + offset, src, chunk);
src += chunk; remaining = (uint16_t)(remaining - chunk); offset = 0;
p = p->next;
}
return (remaining == 0) ? 0 : -1;
}
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset)
{
if (pbuf_copy_partial(p, buf, (uint16_t)len, (uint16_t)offset) == (uint16_t)len) return buf;
return NULL;
}
+41
View File
@@ -0,0 +1,41 @@
// lwip_pbuf.h — simplified packet buffer (PBUF_RAM only)
#ifndef LWIP_PBUF_H
#define LWIP_PBUF_H
#include <stdint.h>
#include <stddef.h>
enum pbuf_layer {
PBUF_TRANSPORT = 54, // 14(ETH) + 20(IP) + 20(TCP)
PBUF_IP = 34, // 14(ETH) + 20(IP)
PBUF_LINK = 14, // 14(ETH)
PBUF_RAW_TX = 54,
PBUF_RAW = 0
};
struct pbuf {
struct pbuf *next;
void *payload;
uint16_t tot_len;
uint16_t len;
uint8_t ref;
uint8_t flags;
uint8_t type_internal;
};
#define PBUF_FLAG_TCP_FIN 0x01U
struct pbuf* pbuf_alloc(uint16_t layer, uint16_t length);
void pbuf_free(struct pbuf *p);
void pbuf_ref(struct pbuf *p);
int pbuf_header(struct pbuf *p, int16_t increment);
int pbuf_header_force(struct pbuf *p, int16_t increment);
void pbuf_chain(struct pbuf *head, struct pbuf *tail);
struct pbuf* pbuf_dechain(struct pbuf *p);
uint16_t pbuf_clen(const struct pbuf *p);
uint16_t pbuf_copy_partial(const struct pbuf *p, void *buf, uint16_t len, uint16_t offset);
int pbuf_take(struct pbuf *p, const void *data, uint16_t len);
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset);
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset);
#endif
File diff suppressed because it is too large. Load diff
+273
View File
@@ -0,0 +1,273 @@
// lwip_tcp.h — lwIP TCP public API (adapted for uTun)
#ifndef LWIP_TCP_H
#define LWIP_TCP_H
#include <stdint.h>
#include <stddef.h>
#include "lwip_pbuf.h"
struct UASYNC;
struct memory_pool;
struct tcp_seg;
struct lwip_tcp_ctx;
enum tcp_state {
CLOSED = 0,
LISTEN = 1,
SYN_SENT = 2,
SYN_RCVD = 3,
ESTABLISHED = 4,
FIN_WAIT_1 = 5,
FIN_WAIT_2 = 6,
CLOSE_WAIT = 7,
CLOSING = 8,
LAST_ACK = 9,
TIME_WAIT = 10
};
typedef uint16_t tcpwnd_size_t;
typedef uint16_t tcpflags_t;
#define TCP_WRITE_FLAG_COPY 0x01
#define TCP_WRITE_FLAG_MORE 0x02
// pcb flags
#define TF_ACK_DELAY 0x01U
#define TF_ACK_NOW 0x02U
#define TF_INFR 0x04U
#define TF_CLOSEPEND 0x08U
#define TF_RXCLOSED 0x10U
#define TF_FIN 0x20U
#define TF_NODELAY 0x40U
#define TF_NAGLEMEMERR 0x80U
#define TF_RTO 0x0100U
// input processing flags (not on pcb->flags)
#define TF_RESET 0x08U
#define TF_CLOSED 0x10U
#define TF_GOT_FIN 0x20U
// Error codes (LERR_ prefix to avoid conflicts with u_async.h and socket_compat.h)
enum tcp_err_enum {
LERR_OK = 0,
LERR_MEM = -1,
LERR_BUF = -2,
LERR_TIMEOUT = -3,
LERR_RTE = -4,
LERR_INPROGRESS = -5,
LERR_VAL = -6,
LERR_WOULDBLOCK = -7,
LERR_USE = -8,
LERR_ALREADY = -9,
LERR_ISCONN = -10,
LERR_CONN = -11,
LERR_IF = -12,
LERR_ABRT = -13,
LERR_RST = -14,
LERR_CLSD = -15,
LERR_ARG = -16
};
typedef int err_t;
// Forward declaration for callbacks
struct tcp_pcb;
// Callbacks
typedef err_t (*tcp_accept_fn) (void *arg, struct tcp_pcb *newpcb, err_t err);
typedef err_t (*tcp_recv_fn) (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
typedef err_t (*tcp_sent_fn) (void *arg, struct tcp_pcb *pcb, uint16_t len);
typedef err_t (*tcp_poll_fn) (void *arg, struct tcp_pcb *pcb);
typedef void (*tcp_err_fn) (void *arg, err_t err);
typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *pcb, err_t err);
// Output callback — called when TCP wants to send an IP packet
typedef err_t (*tcp_output_fn)(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
#define TCP_TRACE_SIZE 384
struct tcp_trace_entry {
uint32_t ticks;
char event;
uint32_t seq;
uint16_t len;
uint16_t wnd;
uint16_t rto;
int16_t rtime;
uint8_t ctx_id;
uint8_t state;
};
struct tcp_trace_buf {
struct tcp_trace_entry e[TCP_TRACE_SIZE];
int pos;
int count;
};
// PCB structures
struct tcp_pcb {
struct tcp_pcb *next;
struct lwip_tcp_ctx *ctx;
void *callback_arg;
enum tcp_state state;
uint8_t prio;
uint16_t local_port;
uint16_t remote_port;
uint32_t local_ip; // host byte order
uint32_t remote_ip; // host byte order
uint8_t ttl;
uint8_t tos;
tcpflags_t flags;
uint8_t polltmr, pollinterval;
uint8_t last_timer;
uint32_t tmr;
uint32_t rcv_nxt;
tcpwnd_size_t rcv_wnd;
tcpwnd_size_t rcv_ann_wnd;
uint32_t rcv_ann_right_edge;
int16_t rtime;
uint16_t mss;
uint32_t rttest;
uint32_t rtseq;
int16_t sa, sv;
int16_t rto;
uint8_t nrtx;
uint8_t dupacks;
uint32_t lastack;
tcpwnd_size_t cwnd;
tcpwnd_size_t ssthresh;
uint32_t rto_end;
uint32_t snd_nxt;
uint32_t snd_wl1, snd_wl2;
uint32_t snd_lbb;
tcpwnd_size_t snd_wnd;
tcpwnd_size_t snd_wnd_max;
tcpwnd_size_t snd_buf;
uint16_t snd_queuelen;
tcpwnd_size_t bytes_acked;
struct tcp_seg *unsent;
struct tcp_seg *unacked;
struct tcp_seg *ooseq;
struct pbuf *refused_data;
struct tcp_pcb_listen *listener;
tcp_sent_fn sent;
tcp_recv_fn recv;
tcp_connected_fn connected;
tcp_poll_fn poll;
tcp_err_fn errf;
uint32_t keep_idle;
uint8_t persist_cnt;
uint8_t persist_backoff;
uint8_t persist_probe;
uint8_t keep_cnt_sent;
};
struct tcp_pcb_listen {
struct tcp_pcb *next;
struct lwip_tcp_ctx *ctx;
void *callback_arg;
enum tcp_state state;
uint8_t prio;
uint16_t local_port;
uint32_t local_ip;
tcp_accept_fn accept;
};
// Context for the TCP module (one per tcp_proxy_client instance)
struct lwip_tcp_ctx {
struct UASYNC *ua;
tcp_output_fn output;
void *output_arg;
void *timer;
struct tcp_pcb *bound_pcbs;
struct tcp_pcb *listen_pcbs;
struct tcp_pcb *active_pcbs;
struct tcp_pcb *tw_pcbs;
struct memory_pool *pcb_pool;
struct memory_pool *pcb_listen_pool;
struct memory_pool *seg_pool;
uint16_t tmr_interval_ms; // default 250 (was TCP_TMR_INTERVAL)
uint16_t rto_min_ms; // default 3000 (initial RTO in ms)
uint16_t rto_max_ms; // default 0 (0=unlimited, cap in ms)
uint32_t ticks;
uint8_t slowtmr_ctr; // for last_timer check (incremented by fasttmr and slowtmr)
uint8_t tmr_phase; // for even/odd slowtmr decision (incremented in tcp_tmr_cb)
uint16_t iss_seed;
uint16_t ip_id;
uint16_t port_seed;
char trace_id; // 'A' or 'B' for trace dump identification
struct tcp_trace_buf trace;
struct {
uint32_t pbuf_fails; // pbuf_alloc → NULL
uint32_t seg_fails; // tcp_create_segment → NULL (seg pool)
uint32_t pcb_fails; // tcp_alloc → NULL (pcb pool)
uint32_t pcb_listen_fails; // tcp_listen pool alloc fail
uint32_t write_fails; // tcp_write → ERR_MEM
uint32_t enq_fails; // tcp_enqueue_flags → ERR_MEM
uint32_t rst_fails; // tcp_rst pbuf alloc fail
uint32_t ack_fails; // tcp_send_empty_ack pbuf fail
uint32_t probe_fails; // tcp_zero_window_probe pbuf fail
uint32_t split_fails; // tcp_split_unsent_seg fail
} stats;
};
// Lifecycle
struct lwip_tcp_ctx* lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void *output_arg);
void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx);
void lwip_tcp_set_timer(struct lwip_tcp_ctx *ctx,
uint16_t interval_ms, uint16_t rto_min_ms, uint16_t rto_max_ms);
// Input: feed an IP packet payload (after IP header) to TCP
void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
uint32_t src_ip, uint32_t dst_ip);
// PCB operations
struct tcp_pcb* tcp_new(struct lwip_tcp_ctx *ctx);
err_t tcp_bind(struct tcp_pcb *pcb, uint32_t ip, uint16_t port);
struct tcp_pcb* tcp_listen(struct tcp_pcb *pcb);
err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ip, uint16_t port, tcp_connected_fn connected);
err_t tcp_write(struct tcp_pcb *pcb, const void *data, uint16_t len, uint8_t apiflags);
err_t tcp_output(struct tcp_pcb *pcb);
err_t tcp_close(struct tcp_pcb *pcb);
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);
// Callback setters
void tcp_arg (struct tcp_pcb *pcb, void *arg);
void tcp_recv (struct tcp_pcb *pcb, tcp_recv_fn recv);
void tcp_sent (struct tcp_pcb *pcb, tcp_sent_fn sent);
void tcp_err (struct tcp_pcb *pcb, tcp_err_fn errf);
void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept);
void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval);
// Helpers
#define tcp_mss(pcb) ((pcb)->mss)
#define tcp_sndbuf(pcb) ((pcb)->snd_buf)
#define tcp_sndqueuelen(pcb) ((pcb)->snd_queuelen)
#define tcp_nagle_disable(pcb) ((pcb)->flags |= TF_NODELAY)
#define tcp_nagle_enable(pcb) ((pcb)->flags &= (tcpflags_t)~TF_NODELAY)
#define tcp_nagle_disabled(pcb) (((pcb)->flags & TF_NODELAY) != 0)
#define tcp_set_flags(pcb, set_flags) ((pcb)->flags |= (tcpflags_t)(set_flags))
#define tcp_clear_flags(pcb, clr_flags) ((pcb)->flags &= (tcpflags_t)~(clr_flags))
#define tcp_is_flag_set(pcb, flag) (((pcb)->flags & (flag)) != 0)
#endif
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
// lwip_tcp_opts.h — hardcoded TCP configuration (no #ifdefs)
#ifndef LWIP_TCP_OPTS_H
#define LWIP_TCP_OPTS_H
#define TCP_MSS 1460
#define TCP_SND_BUF (16 * TCP_MSS)
#define TCP_WND (8 * TCP_MSS)
#define TCP_SND_QUEUELEN (4 * TCP_SND_BUF / TCP_MSS)
#define TCP_SNDLOWAT (2 * TCP_MSS < TCP_SND_BUF / 2 ? 2 * TCP_MSS : TCP_SND_BUF / 2)
#define TCP_MAXRTX 12
#define TCP_SYNMAXRTX 6
#define TCP_TMR_INTERVAL 250 // ms for tcp_tmr
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
#define TCP_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 (2*MSL = 120s)
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
#define TCP_KEEPINTVL_DEFAULT 75000
#define TCP_KEEPCNT_DEFAULT 9
#define TCP_WND_SCALE 0 // disabled
#define TCP_TIMESTAMPS 0 // disabled
#define TCP_SACK_OUT 0 // disabled
#define TCP_QUEUE_OOSEQ 1
#define TCP_OVERSIZE 0
#define TCP_LISTEN_BACKLOG 0
#define TCP_KEEPALIVE 0
#define TCP_CHECKSUM_ON_COPY 0
#define TCP_CALCULATE_EFF_SEND_MSS 0
#define TCP_LOCAL_PORT_RANGE_START 0xc000
#define TCP_LOCAL_PORT_RANGE_END 0xffff
#if TCP_MSS > 536
#define INITIAL_MSS 536
#else
#define INITIAL_MSS TCP_MSS
#endif
#define TCP_PRIO_MIN 1
#define TCP_PRIO_NORMAL 64
#define TCP_PRIO_MAX 127
#endif
File diff suppressed because it is too large. Load diff
+251
View File
@@ -0,0 +1,251 @@
// lwip_tcp_priv.h — lwIP TCP internal API
#ifndef LWIP_TCP_PRIV_H
#define LWIP_TCP_PRIV_H
#include "lwip_tcp.h"
#include "lwip_tcp_opts.h"
#include <string.h>
// Packed struct support
#ifndef PACK_STRUCT_BEGIN
#define PACK_STRUCT_BEGIN
#endif
#ifndef PACK_STRUCT_END
#define PACK_STRUCT_END
#endif
#ifndef PACK_STRUCT_FIELD
#define PACK_STRUCT_FIELD(x) x
#endif
#ifndef PACK_STRUCT_STRUCT
#if defined(__GNUC__) || defined(__clang__)
#define PACK_STRUCT_STRUCT __attribute__((packed))
#else
#define PACK_STRUCT_STRUCT
#endif
#endif
#ifndef _WIN32
#include <arpa/inet.h>
#else
#define htons(x) ((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF))
#define ntohs(x) htons(x)
#define htonl(x) ((((x) & 0xFF) << 24) | (((x) & 0xFF00) << 8) | (((x) >> 8) & 0xFF00) | (((x) >> 24) & 0xFF))
#define ntohl(x) htonl(x)
#endif
// TCP header (wire format)
PACK_STRUCT_BEGIN
struct tcp_hdr {
PACK_STRUCT_FIELD(uint16_t src);
PACK_STRUCT_FIELD(uint16_t dest);
PACK_STRUCT_FIELD(uint32_t seqno);
PACK_STRUCT_FIELD(uint32_t ackno);
PACK_STRUCT_FIELD(uint16_t _hdrlen_rsvd_flags);
PACK_STRUCT_FIELD(uint16_t wnd);
PACK_STRUCT_FIELD(uint16_t chksum);
PACK_STRUCT_FIELD(uint16_t urgp);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define TCP_FIN 0x01U
#define TCP_SYN 0x02U
#define TCP_RST 0x04U
#define TCP_PSH 0x08U
#define TCP_ACK 0x10U
#define TCP_URG 0x20U
#define TCP_ECE 0x40U
#define TCP_CWR 0x80U
#define TCP_FLAGS 0x3fU
#define TCPH_HDRLEN(phdr) ((uint16_t)(ntohs((phdr)->_hdrlen_rsvd_flags) >> 12))
#define TCPH_HDRLEN_BYTES(phdr) ((uint8_t)(TCPH_HDRLEN(phdr) << 2))
#define TCPH_FLAGS(phdr) ((uint8_t)(ntohs((phdr)->_hdrlen_rsvd_flags) & TCP_FLAGS))
#define TCPH_HDRLEN_SET(phdr, len) (phdr)->_hdrlen_rsvd_flags = htons(((len) << 12) | TCPH_FLAGS(phdr))
#define TCPH_FLAGS_SET(phdr, flags) (phdr)->_hdrlen_rsvd_flags = (((phdr)->_hdrlen_rsvd_flags & htons((uint16_t)~TCP_FLAGS)) | htons(flags))
#define TCPH_HDRLEN_FLAGS_SET(phdr, len, flags) (phdr)->_hdrlen_rsvd_flags = htons((uint16_t)(((uint16_t)(len) << 12) | (flags)))
#define TCPH_SET_FLAG(phdr, flags) (phdr)->_hdrlen_rsvd_flags = ((phdr)->_hdrlen_rsvd_flags | htons(flags))
#define TCPH_UNSET_FLAG(phdr, flags) (phdr)->_hdrlen_rsvd_flags = ((phdr)->_hdrlen_rsvd_flags & ~htons(flags))
// TCP header length
#define TCP_HLEN 20
// Sequence number macros
#define TCP_SEQ_LT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) < 0)
#define TCP_SEQ_LEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) <= 0)
#define TCP_SEQ_GT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) > 0)
#define TCP_SEQ_GEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) >= 0)
#define TCP_SEQ_BETWEEN(a,b,c) (TCP_SEQ_GEQ(a,b) && TCP_SEQ_LEQ(a,c))
// TCP options
#define TCP_OPT_END 0
#define TCP_OPT_NOP 1
#define TCP_OPT_MSS 2
#define TCP_OPT_MSS_LEN 4
// tcp_seg flags
#define TF_SEG_OPTS_MSS 0x01U
// tcp_seg structure
struct tcp_seg {
struct tcp_seg *next;
struct pbuf *p;
uint16_t len;
uint8_t flags;
struct tcp_hdr *tcphdr;
};
#define TCP_TCPLEN(seg) ((seg)->len + (((TCPH_FLAGS((seg)->tcphdr) & (TCP_FIN | TCP_SYN)) != 0) ? 1U : 0U))
// Window macros
#define TCPWND16(x) (x)
#define TCP_WND_MAX(pcb) TCP_WND
#define RCV_WND_SCALE(pcb, wnd) (wnd)
#define SND_WND_SCALE(pcb, wnd) (wnd)
#define TCP_WND_INC(wnd, inc) do { \
if ((tcpwnd_size_t)(wnd + inc) >= wnd) { wnd = (tcpwnd_size_t)(wnd + inc); } \
else { wnd = (tcpwnd_size_t)-1; } \
} while(0)
// TCP_STATE_IS_CLOSING
#define TCP_STATE_IS_CLOSING(state) ((state) >= FIN_WAIT_1)
// Nagle
#define tcp_do_output_nagle(tpcb) ((((tpcb)->unacked == NULL) || \
((tpcb)->flags & (TF_NODELAY | TF_INFR)) || \
(((tpcb)->unsent != NULL) && (((tpcb)->unsent->next != NULL) || \
((tpcb)->unsent->len >= (tpcb)->mss))) || \
(tcp_sndbuf(tpcb) == 0 || tcp_sndqueuelen(tpcb) >= TCP_SND_QUEUELEN)) ? 1 : 0)
#define tcp_output_nagle(tpcb) (tcp_do_output_nagle(tpcb) ? tcp_output(tpcb) : LERR_OK)
// Callback event macros
#define TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret) \
do { if((lpcb)->accept != NULL) (ret) = (lpcb)->accept((arg),(pcb),(err)); else (ret) = LERR_ARG; } while(0)
#define TCP_EVENT_SENT(pcb,space,ret) \
do { if((pcb)->sent != NULL) (ret) = (pcb)->sent((pcb)->callback_arg,(pcb),(space)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_RECV(pcb,p,err,ret) \
do { if((pcb)->recv != NULL) { (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err)); } \
else { (ret) = LERR_OK; if(p) pbuf_free(p); } } while(0)
#define TCP_EVENT_CLOSED(pcb,ret) \
do { if(((pcb)->recv != NULL)) { (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),NULL,LERR_OK); } \
else { (ret) = LERR_OK; } } while(0)
#define TCP_EVENT_CONNECTED(pcb,err,ret) \
do { if((pcb)->connected != NULL) (ret) = (pcb)->connected((pcb)->callback_arg,(pcb),(err)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_POLL(pcb,ret) \
do { if((pcb)->poll != NULL) (ret) = (pcb)->poll((pcb)->callback_arg,(pcb)); else (ret) = LERR_OK; } while(0)
#define TCP_EVENT_ERR(last_state,errf,arg,err) \
do { (void)(last_state); if((errf) != NULL) (errf)((arg),(err)); } while(0)
// tcp_ack / tcp_ack_now
#define tcp_ack(pcb) do { \
if((pcb)->flags & TF_ACK_DELAY) { tcp_clear_flags(pcb, TF_ACK_DELAY); tcp_ack_now(pcb); } \
else { tcp_set_flags(pcb, TF_ACK_DELAY); } \
} while(0)
#define tcp_ack_now(pcb) tcp_set_flags(pcb, TF_ACK_NOW)
// TCP timer intervals
#define TCP_TMR_INTERVAL_MS TCP_TMR_INTERVAL
#define TCP_FAST_INTERVAL_MS TCP_FAST_INTERVAL
#define TCP_SLOW_INTERVAL_MS TCP_SLOW_INTERVAL
#define TCP_FIN_WAIT_TIMEOUT_MS TCP_FIN_WAIT_TIMEOUT
#define TCP_SYN_RCVD_TIMEOUT_MS TCP_SYN_RCVD_TIMEOUT
#define TCP_MSL_MS TCP_MSL
// build MSS option
#define TCP_BUILD_MSS_OPTION(mss) htonl(0x02040000 | ((mss) & 0xFFFF))
// IP header
PACK_STRUCT_BEGIN
struct ip_hdr {
uint8_t vhl;
uint8_t tos;
uint16_t len;
uint16_t id;
uint16_t offset;
uint8_t ttl;
uint8_t proto;
uint16_t chksum;
uint32_t src;
uint32_t dst;
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define IP_PROTO_TCP 6
// TCP backoff arrays
extern const uint8_t tcp_backoff[13];
extern const uint8_t tcp_persist_backoff[7];
// Internal functions
err_t tcp_send_empty_ack(struct tcp_pcb *pcb);
err_t tcp_send_fin(struct tcp_pcb *pcb);
err_t tcp_enqueue_flags(struct tcp_pcb *pcb, uint8_t flags);
void tcp_rst(struct tcp_pcb *pcb, uint32_t seqno, uint32_t ackno,
uint32_t local_ip, uint32_t remote_ip,
uint16_t local_port, uint16_t remote_port);
err_t tcp_rexmit(struct tcp_pcb *pcb);
err_t tcp_rexmit_rto_prepare(struct tcp_pcb *pcb);
void tcp_rexmit_rto_commit(struct tcp_pcb *pcb);
void tcp_rexmit_rto(struct tcp_pcb *pcb);
void tcp_rexmit_fast(struct tcp_pcb *pcb);
void tcp_rexmit_seg(struct tcp_pcb *pcb, struct tcp_seg *seg);
err_t tcp_keepalive(struct tcp_pcb *pcb);
err_t tcp_zero_window_probe(struct tcp_pcb *pcb);
err_t tcp_split_unsent_seg(struct tcp_pcb *pcb, uint16_t split);
void tcp_trigger_input_pcb_close(void);
err_t tcp_process_refused_data(struct tcp_pcb *pcb);
// Segment management
struct tcp_pcb* tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio);
void tcp_free(struct tcp_pcb *pcb);
void tcp_pcb_purge(struct tcp_pcb *pcb);
void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb);
void tcp_segs_free(struct tcp_seg *seg);
void tcp_seg_free(struct tcp_seg *seg);
struct tcp_seg* tcp_seg_copy(struct tcp_seg *seg);
void tcp_free_ooseq(struct tcp_pcb *pcb);
// Timer internal
void tcp_fasttmr(struct lwip_tcp_ctx *ctx);
void tcp_slowtmr(struct lwip_tcp_ctx *ctx);
void tcp_txnow(struct lwip_tcp_ctx *ctx);
// Utils
uint32_t tcp_next_iss(struct tcp_pcb *pcb);
uint16_t tcp_checksum(const void *data, uint16_t len);
uint16_t tcp_ip_checksum(const struct ip_hdr *iph);
// Trace hook — set by test to capture internal TCP events (ring buffer)
typedef void (*tcp_trace_fn)(struct lwip_tcp_ctx *ctx, char event, uint32_t seq, uint16_t len, uint16_t wnd, const char *msg);
extern tcp_trace_fn g_tcp_trace;
void lwip_tcp_trace_record(struct lwip_tcp_ctx *ctx, char event, uint32_t seq,
uint16_t len, uint16_t wnd, uint16_t rto, int16_t rtime, uint8_t state);
void lwip_tcp_trace_dump(struct lwip_tcp_ctx *ctx);
void lwip_tcp_trace_clear(struct lwip_tcp_ctx *ctx);
void lwip_tcp_stats_dump(struct lwip_tcp_ctx *ctx);
void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
// PCB list management
#define TCP_REG(pcbs, npcb) do { \
(npcb)->next = *(pcbs); *(pcbs) = (npcb); \
} while(0)
#define TCP_RMV(pcbs, npcb) do { \
if(*(pcbs) == (npcb)) { *(pcbs) = (*pcbs)->next; } \
else { struct tcp_pcb *_tmp; for(_tmp = *(pcbs); _tmp != NULL; _tmp = _tmp->next) { if(_tmp->next == (npcb)) { _tmp->next = (npcb)->next; break; } } } \
(npcb)->next = NULL; \
} while(0)
#define TCP_REG_ACTIVE(ctx, npcb) TCP_REG(&(ctx)->active_pcbs, npcb)
#define TCP_RMV_ACTIVE(ctx, npcb) TCP_RMV(&(ctx)->active_pcbs, npcb)
#endif
+211
View File
@@ -0,0 +1,211 @@
#include "nat_transport.h"
#include "eim_nat.h"
#include "etcp.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "tun_if.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/ll_queue.h"
#include <string.h>
#define NAT_SVC_HDR_SIZE 9 // svc_id(1) + src_node_id(8)
static ip_str_t ip_host_to_str(uint32_t ip_host) {
struct in_addr a; a.s_addr = htonl(ip_host);
return ip_to_str(&a, AF_INET);
}
// ==================== Callbacks ====================
// CLIENT: NAT TUN output → encapsulate in ETCP_ID_NAT → send to provider via etcp_router
static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
if (!inst) { queue_resume_callback(q); return; }
struct nat_transport_ctx* tr = &inst->nat_tr;
struct ll_entry* pkt = queue_data_get(q);
if (!pkt) { queue_resume_callback(q); return; }
if (!pkt->dgram || pkt->len < 2) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
size_t ip_len = pkt->len - 1;
size_t total_len = NAT_SVC_HDR_SIZE + ip_len;
uint8_t* new_dgram = u_malloc(total_len);
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_dgram[0] = ETCP_ID_NAT;
memcpy(new_dgram + 1, &tr->self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
struct ll_entry* new_entry = queue_entry_new(0);
if (!new_entry) { u_free(new_dgram); queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_entry->dgram = new_dgram;
new_entry->len = total_len;
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry, 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);
queue_entry_free(new_entry); queue_dgram_free(new_entry);
}
}
// PROVIDER: NAT TUN output (internet response) → ingress NAT → send back via etcp_router
static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
if (!inst) { queue_resume_callback(q); return; }
struct eim_nat_ctx* ctx = &inst->nat;
struct ll_entry* pkt = queue_data_get(q);
if (!pkt) { queue_resume_callback(q); return; }
if (!pkt->dgram || pkt->len < 2) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
struct eim_nat_entry* entry = NULL;
int ret = eim_nat_ingress(ctx, pkt->dgram + 1, pkt->len - 1, &entry);
if (ret != 0 || !entry || entry->src_node_id == 0) {
if (ret == 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "Ingress NAT: no matching entry, dropping");
queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q);
return;
}
size_t ip_len = pkt->len - 1;
size_t total_len = NAT_SVC_HDR_SIZE + ip_len;
uint8_t* new_dgram = u_malloc(total_len);
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_dgram[0] = ETCP_ID_NAT;
memcpy(new_dgram + 1, &inst->nat_tr.self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
struct ll_entry* new_entry = queue_entry_new(0);
if (!new_entry) { u_free(new_dgram); queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_entry->dgram = new_dgram;
new_entry->len = total_len;
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry, 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);
queue_entry_free(new_entry); queue_dgram_free(new_entry);
}
}
// ETCP_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!conn || !entry || !entry->dgram || entry->len < NAT_SVC_HDR_SIZE) {
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
return;
}
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) { queue_entry_free(entry); queue_dgram_free(entry); return; }
struct nat_transport_ctx* tr = &inst->nat_tr;
struct eim_nat_ctx* ctx = &inst->nat;
if (!ctx->initialized) { queue_entry_free(entry); queue_dgram_free(entry); return; }
uint64_t src_node_id;
memcpy(&src_node_id, entry->dgram + 1, 8);
uint8_t* ip_data = entry->dgram + NAT_SVC_HDR_SIZE;
size_t ip_len = entry->len - NAT_SVC_HDR_SIZE;
if (tr->nat_via_node_id != 0) {
if (tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + NAT_SVC_HDR_SIZE - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT client: received %zu bytes from provider", ip_len);
}
} else {
int ret = eim_nat_egress(ctx, ip_data, ip_len, src_node_id, conn);
if (ret < 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "Egress NAT failed");
} else if (ret == 0 && tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + NAT_SVC_HDR_SIZE - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: sent %zu bytes to internet", ip_len);
}
}
queue_dgram_free(entry); queue_entry_free(entry);
}
// ==================== Init / Destroy ====================
int nat_transport_init(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->config) return -1;
struct nat_transport_ctx* tr = &inst->nat_tr;
struct global_config* g = &inst->config->global;
memset(tr, 0, sizeof(*tr));
if (!g->nat_enabled) return 0;
tr->self_node_id = inst->node_id;
tr->nat_via_node_id = g->nat_via_node_id;
if (tr->nat_via_node_id == 0) {
if (eim_nat_init_ctx(&inst->nat, g) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to init NAT engine");
return -1;
}
} else {
inst->nat.initialized = 1;
}
const char* tun_name = g->nat_tun_ifname[0] ? g->nat_tun_ifname : "tun_nat";
char ip_str[64] = "";
if (g->nat_tun_ip.family == AF_INET) {
snprintf(ip_str, sizeof(ip_str), "%s", ip_to_str(&g->nat_tun_ip.addr.v4, AF_INET).str);
} else if (g->tun_ip.family == AF_INET) {
snprintf(ip_str, sizeof(ip_str), "%s", ip_to_str(&g->tun_ip.addr.v4, AF_INET).str);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No NAT TUN IP");
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
tr->nat_tun = tun_init_nat(inst->ua, tun_name, ip_str, g->mtu, g->tun_test_mode);
if (!tr->nat_tun) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to create NAT TUN %s ip=%s", tun_name, ip_str);
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
if (etcp_router_bind(inst, ETCP_ID_NAT, nat_transport_etcp_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to bind ETCP_ID_NAT via etcp_router");
tun_close(tr->nat_tun); tr->nat_tun = NULL;
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
struct eim_nat_ctx* ctx = &inst->nat;
if (tr->nat_via_node_id != 0) {
if (tr->nat_tun->output_queue) queue_set_callback(tr->nat_tun->output_queue, nat_transport_client_tun_out_cb, inst);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT client via node %016llx, TUN=%s gw=%s ports=%u-%u",
(unsigned long long)tr->nat_via_node_id, tun_name,
ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
} else {
if (tr->nat_tun->output_queue) queue_set_callback(tr->nat_tun->output_queue, nat_transport_provider_tun_out_cb, inst);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT provider TUN=%s gw=%s ports=%u-%u",
tun_name, ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
}
tr->initialized = 1;
return 0;
}
void nat_transport_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->nat_tr.initialized) return;
struct nat_transport_ctx* tr = &inst->nat_tr;
etcp_router_unbind(inst, ETCP_ID_NAT);
if (tr->nat_tun) { tun_close(tr->nat_tun); tr->nat_tun = NULL; }
eim_nat_destroy_ctx(&inst->nat);
memset(tr, 0, sizeof(*tr));
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT transport destroyed");
}
+22
View File
@@ -0,0 +1,22 @@
// nat_transport.h — NAT transport layer (TUN + ETCP protocol handling)
#ifndef NAT_TRANSPORT_H
#define NAT_TRANSPORT_H
#include <stdint.h>
struct tun_if;
struct ETCP_CONN;
struct nat_transport_ctx {
uint64_t self_node_id;
uint64_t nat_via_node_id;
struct tun_if* nat_tun;
int initialized;
};
struct UTUN_INSTANCE;
int nat_transport_init(struct UTUN_INSTANCE* inst);
void nat_transport_destroy(struct UTUN_INSTANCE* inst);
#endif
+14 -16
View File
@@ -30,7 +30,6 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
struct PKTNORM* pn = u_calloc(1, sizeof(struct PKTNORM));
if (!pn) {
pn->alloc_errors++;
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: calloc failed");
return NULL;
}
@@ -40,17 +39,22 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->etcp = etcp;
pn->ua = etcp->instance->ua;
pn->frag_size = etcp->mtu - 100; // Use MTU as fixed packet size (adjust if headers need subtraction)
pn->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
int min_frag = UDP_HDR_SIZE + UDP_SC_HDR_SIZE + ACK_REZERV;
if (etcp->mtu < min_frag || pn->frag_size > etcp->mtu) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: MTU %d too small (min %d)", etcp->mtu, min_frag);
pn->frag_size = etcp->mtu;
}
pn->tx_wait_time = 10;
pn->input = queue_new(pn->ua, 0, "pn_input"); // No hash needed
pn->input = queue_new(pn->ua, 0, 0, 0, "pn_input"); // No hash needed
if (!pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: queue_new(input) failed");
pn_deinit(pn);
return NULL;
}
pn->output = queue_new(pn->ua, 0, "pn_output"); // No hash needed
pn->output = queue_new(pn->ua, 0, 0, 0, "pn_output"); // No hash needed
if (!pn->output) {
DEBUG_ERROR(DEBUG_CATEGORY_NORMALIZER, "pn_init: queue_new(output) failed");
pn_deinit(pn);
@@ -65,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;
}
@@ -116,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);
@@ -169,17 +175,8 @@ void pn_reset(struct PKTNORM* pn) {
// Reset unpacker state
pn_unpacker_reset_state(pn);
// Drain input and output queues to discard pending data and maintain consistency
struct ll_entry* entry;
while ((entry = queue_data_get(pn->input)) != NULL) {
queue_dgram_free(entry);
queue_entry_free(entry);
}
while ((entry = queue_data_get(pn->output)) != NULL) {
queue_dgram_free(entry);
queue_entry_free(entry);
}
queue_resume_callback(pn->input);
queue_resume_callback(pn->output);
}
// Send data to packer (copies and adds to input queue or pending, triggering callback) используется только в юниттесте
@@ -218,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
@@ -307,6 +304,7 @@ static void etcp_input_ready_cb(struct ll_queue* q, void* arg) {
pn->data_ptr += remain;
in_ptr += remain;
pn_buf_renew(pn);
if (!pn->data) goto exit;
}
exit:
+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
```
+343
View File
@@ -0,0 +1,343 @@
// icmp_proxy.c — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
#include "icmp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "tun_if.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
#define ICMP_DEF_TTL 64
#define ICMP_TIMEOUT_TB 50000 // 5s for echo reply
static struct icmp_proxy_ctx* g_icmp_ctx = NULL;
static void req_expire_timer_cb(void* arg);
static void req_expire(struct icmp_proxy_ctx* ctx);
static struct icmp_request* req_find_by_id(struct icmp_request* head, uint16_t echo_id, uint16_t echo_seq) {
struct icmp_request* r;
for (r = head; r; r = r->next) if (r->echo_id == echo_id && r->echo_seq == echo_seq) return r;
return NULL;
}
// Сборка и отправка raw ICMP echo запроса
static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return -1;
size_t icmp_len = ICMP_MINLEN + payload_len;
uint8_t* buf = u_malloc(icmp_len);
if (!buf) return -1;
struct icmp* icmp_hdr = (struct icmp*)buf;
memset(icmp_hdr, 0, icmp_len);
icmp_hdr->icmp_type = ICMP_ECHO;
icmp_hdr->icmp_code = 0;
icmp_hdr->icmp_id = echo_id;
icmp_hdr->icmp_seq = echo_seq;
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
icmp_hdr->icmp_cksum = 0;
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto dst=0x%08x id=0x%04x seq=%u len=%zu",
dst_ip, echo_id, echo_seq, icmp_len);
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}};
ssize_t n = sendto(g_icmp_ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr));
u_free(buf);
if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto failed: %s", strerror(errno)); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto sent %zd bytes", n);
struct icmp_request* r = u_calloc(1, sizeof(struct icmp_request));
if (!r) return 0;
r->client_node_id = client_node_id; r->dst_ip = dst_ip; r->orig_src_ip = orig_src_ip;
r->echo_id = echo_id; r->echo_seq = echo_seq;
r->payload_len = payload_len > sizeof(r->payload) ? sizeof(r->payload) : payload_len;
if (payload_len > 0) memcpy(r->payload, payload, r->payload_len);
r->sent_tb = get_time_tb();
r->next = g_icmp_ctx->pending; g_icmp_ctx->pending = r;
if (!g_icmp_ctx->expire_timer)
g_icmp_ctx->expire_timer = uasync_set_timeout(g_icmp_ctx->ua, g_icmp_ctx->request_timeout_tb, g_icmp_ctx, req_expire_timer_cb, "icmp_expire");
return 0;
}
// Чтение echo ответа из raw сокета, сопоставление по echo_id
static void raw_read_cb(socket_t sock, void* arg) {
(void)sock; (void)arg;
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return;
uint8_t buf[65536];
struct sockaddr_in from; socklen_t flen = sizeof(from);
ssize_t n = recvfrom(g_icmp_ctx->raw_sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
if (n <= 0) { if (n < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: recvfrom error: %s", strerror(errno)); return; }
if (n < (ssize_t)(sizeof(struct ip) + ICMP_MINLEN)) return;
struct ip* ip_hdr = (struct ip*)buf;
if (ip_hdr->ip_p != IPPROTO_ICMP) return;
size_t ip_hdr_len = ip_hdr->ip_hl * 4;
if (n < (ssize_t)(ip_hdr_len + ICMP_MINLEN)) return;
struct icmp* icmp_hdr = (struct icmp*)(buf + ip_hdr_len);
if (icmp_hdr->icmp_type != ICMP_ECHOREPLY) return;
struct icmp_request* r = req_find_by_id(g_icmp_ctx->pending, icmp_hdr->icmp_id, icmp_hdr->icmp_seq);
if (!r) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "icmp_proxy: unclaimed echo reply id=0x%04x seq=%u from=0x%08x",
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr); return; }
{ struct icmp_request** rp = &g_icmp_ctx->pending;
while (*rp) { if (*rp == r) { *rp = r->next; break; } rp = &(*rp)->next; } }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: echo reply id=0x%04x seq=%u from=0x%08x",
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr);
size_t payload_len = n - ip_hdr_len - ICMP_MINLEN;
if (payload_len > 1500) payload_len = 1500;
uint8_t* payload = (payload_len > 0) ? (uint8_t*)(icmp_hdr->icmp_data) : NULL;
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
memcpy(e->dgram + 2, &r->client_node_id, 8);
memcpy(e->dgram + 10, &r->dst_ip, 4);
memcpy(e->dgram + 14, &r->orig_src_ip, 4);
memcpy(e->dgram + 18, &icmp_hdr->icmp_id, 2);
memcpy(e->dgram + 20, &icmp_hdr->icmp_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e, 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);
}
// ====================================================================
// Exit узел: принять REQUEST, отправить echo через raw сокет
// ====================================================================
static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
if (!inst || !g_icmp_ctx || entry->len < ICMP_PROXY_HDR_SIZE + 1) goto drop;
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8);
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 10, 4);
uint32_t orig_src_ip; memcpy(&orig_src_ip, entry->dgram + 14, 4);
uint16_t echo_id; memcpy(&echo_id, entry->dgram + 18, 2);
uint16_t echo_seq; memcpy(&echo_seq, entry->dgram + 20, 2);
uint8_t* payload = entry->dgram + ICMP_PROXY_HDR_SIZE;
size_t payload_len = entry->len - ICMP_PROXY_HDR_SIZE;
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
exit_send_echo(inst, client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, payload_len);
} else if (g_icmp_ctx->test_loopback) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: test loopback reply to 0x%08x", dst_ip);
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (e->dgram) {
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
memcpy(e->dgram + 2, &client_node_id, 8);
memcpy(e->dgram + 10, &dst_ip, 4);
memcpy(e->dgram + 14, &orig_src_ip, 4);
memcpy(e->dgram + 18, &echo_id, 2);
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
etcp_route_send(inst, client_node_id, e, 0);
} else queue_entry_free(e);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: no raw socket, dropping echo request to 0x%08x", dst_ip);
}
drop:
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: принять REPLY, доставить echo ответ в TUN
// ====================================================================
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
uint32_t src_ip;
uint32_t orig_src_ip;
uint16_t echo_id, echo_seq;
memcpy(&src_ip, entry->dgram + 10, 4);
memcpy(&orig_src_ip, entry->dgram + 14, 4);
memcpy(&echo_id, entry->dgram + 18, 2);
memcpy(&echo_seq, entry->dgram + 20, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: client got reply id=0x%04x seq=%u from=0x%08x dst=0x%08x",
echo_id, echo_seq, src_ip, orig_src_ip);
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
icmp_proxy_deliver_reply(inst, orig_src_ip, src_ip, echo_id, echo_seq,
entry->dgram + ICMP_PROXY_HDR_SIZE, entry->len - ICMP_PROXY_HDR_SIZE);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Единый etcp_router коллбэк
// ====================================================================
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < 2) {
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
if (subcmd == ICMP_PROXY_SUBCMD_REQUEST) { exit_handle_request(conn, entry); return; }
if (subcmd == ICMP_PROXY_SUBCMD_REPLY) { client_handle_reply(conn, entry); return; }
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: отправить ICMP echo запрос в exit узел
// ====================================================================
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!inst) return -1;
struct ll_entry* e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REQUEST;
memcpy(e->dgram + 2, &inst->node_id, 8);
memcpy(e->dgram + 10, &dst_ip, 4);
memcpy(e->dgram + 14, &orig_src_ip, 4);
memcpy(e->dgram + 18, &echo_id, 2);
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, exit_node_id, e, 0);
}
// ====================================================================
// Сторона клиента: доставить ICMP echo ответ в TUN
// ====================================================================
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
return -1;
}
struct tun_if* tun = inst->tcp_proxy_client->tun;
size_t icmp_len = ICMP_MINLEN + payload_len;
size_t pkt_len = 20 + icmp_len;
uint8_t* pkt = u_malloc(pkt_len);
if (!pkt) return -1;
memset(pkt, 0, 20);
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_ICMP;
uint16_t total_len = htons(20 + icmp_len);
memcpy(pkt + 2, &total_len, 2);
memcpy(pkt + 12, &src_ip, 4);
memcpy(pkt + 16, &dst_ip, 4);
{ uint32_t ip_sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); ip_sum += w; }
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16); ip_sum += (ip_sum >> 16);
uint16_t cs = (uint16_t)(~ip_sum); memcpy(pkt + 10, &cs, 2); }
struct icmp* icmp_hdr = (struct icmp*)(pkt + 20);
icmp_hdr->icmp_type = ICMP_ECHOREPLY; icmp_hdr->icmp_code = 0;
icmp_hdr->icmp_id = echo_id; icmp_hdr->icmp_seq = echo_seq;
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
icmp_hdr->icmp_cksum = 0;
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(pkt); return -1; }
e->dgram = u_malloc(1 + pkt_len);
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len;
u_free(pkt);
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;
}
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled) {
(void)inst;
if (g_icmp_ctx) g_icmp_ctx->test_loopback = enabled;
}
static void req_expire_timer_cb(void* arg) {
struct icmp_proxy_ctx* ctx = (struct icmp_proxy_ctx*)arg;
req_expire(ctx);
ctx->expire_timer = NULL;
if (ctx->pending)
ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire");
}
static void req_expire(struct icmp_proxy_ctx* ctx) {
uint64_t now = get_time_tb(); struct icmp_request** prev = &ctx->pending;
while (*prev) {
struct icmp_request* r = *prev;
if (now - r->sent_tb > ctx->request_timeout_tb) { *prev = r->next; u_free(r); }
else prev = &r->next;
}
}
// ====================================================================
// Публичные init/destroy
// ====================================================================
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
if (!inst) return -1;
struct icmp_proxy_ctx* ctx = u_calloc(1, sizeof(struct icmp_proxy_ctx));
if (!ctx) return -1;
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID;
ctx->request_timeout_tb = ICMP_TIMEOUT_TB;
ctx->is_exit = inst->tcp_proxy_server.enabled;
ctx->test_loopback = 0;
ctx->expire_timer = NULL;
g_icmp_ctx = ctx;
if (ctx->is_exit) {
ctx->raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (ctx->raw_sock == SOCKET_INVALID)
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket(SOCK_RAW) failed: %s", strerror(errno));
else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket created fd=%d", ctx->raw_sock);
ctx->raw_read_id = uasync_add_socket(ua, ctx->raw_sock, raw_read_cb, NULL, NULL, NULL);
if (!ctx->raw_read_id) { socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; }
}
}
etcp_router_bind(inst, ETCP_ID_ICMP_PROXY, icmp_proxy_recv_cb);
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock);
return 0;
}
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !g_icmp_ctx) return;
etcp_router_unbind(inst, ETCP_ID_ICMP_PROXY);
if (g_icmp_ctx->expire_timer) { uasync_cancel_timeout(g_icmp_ctx->ua, g_icmp_ctx->expire_timer); g_icmp_ctx->expire_timer = NULL; }
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
if (g_icmp_ctx->raw_read_id) { uasync_remove_socket_t(g_icmp_ctx->ua, g_icmp_ctx->raw_sock); g_icmp_ctx->raw_read_id = NULL; }
socket_close_wrapper(g_icmp_ctx->raw_sock);
}
struct icmp_request* r = g_icmp_ctx->pending;
while (r) { struct icmp_request* n = r->next; u_free(r); r = n; }
u_free(g_icmp_ctx); g_icmp_ctx = NULL;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy destroyed");
}
+65
View File
@@ -0,0 +1,65 @@
// icmp_proxy.h — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
#ifndef ICMP_PROXY_H
#define ICMP_PROXY_H
#include <stdint.h>
#include "../lib/socket_compat.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
// Подкоманды
#define ICMP_PROXY_SUBCMD_REQUEST 0x01 // client→exit: пингани dst_ip
#define ICMP_PROXY_SUBCMD_REPLY 0x02 // exit→client: echo reply
// Заголовок сообщения (не включая байт svc_id)
// svc_id(1) + subcmd(1) + sender_node_id(8) + dst_ip(4) + orig_src_ip(4) + icmp_id(2) + icmp_seq(2) + payload
#define ICMP_PROXY_HDR_SIZE 22
// Активный ICMP echo запрос (сторона exit узла)
struct icmp_request {
struct icmp_request* next;
uint64_t client_node_id;
uint32_t dst_ip;
uint32_t orig_src_ip; // IP исходного отправителя (чтобы вернуть reply правильному адресату)
uint16_t echo_id;
uint16_t echo_seq;
uint8_t payload[1500];
size_t payload_len;
uint64_t sent_tb;
socket_t sock; // raw сокет (только если per-request)
};
struct icmp_proxy_ctx {
int initialized;
int is_exit;
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
socket_t raw_sock; // один SOCK_RAW для всего ICMP на exit
void* raw_read_id;
struct icmp_request* pending;
uint64_t request_timeout_tb;
int test_loopback; // 1 = виртуальный loopback без raw сокета (только тесты)
void* expire_timer; // периодический таймер очистки истёкших запросов
};
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua);
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst);
// Единый etcp_router коллбэк
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
// Сторона клиента: принять IP/ICMP echo request с TUN, отправить через etcp
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len);
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len);
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled);
#endif // ICMP_PROXY_H
+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
+264
View File
@@ -0,0 +1,264 @@
// udp_proxy.c — UDP датаграммный прокси: клиент ↔ exit через etcp_router
#include "udp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "tun_if.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
#define UDP_FLOW_TIMEOUT_TB 600000 // 60с
static struct udp_proxy_ctx* g_udp_ctx = NULL;
static void flow_expire_timer_cb(void* arg);
static void flow_expire(struct udp_proxy_ctx* ctx);
static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id,
uint32_t src_ip, uint16_t src_port,
uint32_t dst_ip, uint16_t dst_port) {
struct udp_flow* f;
for (f = head; f; f = f->next)
if (f->client_node_id == client_node_id && f->src_ip == src_ip &&
f->src_port == src_port && f->dst_ip == dst_ip && f->dst_port == dst_port) return f;
return NULL;
}
static void flow_read_cb(socket_t sock, void* arg) {
(void)sock; struct udp_flow* f = (struct udp_flow*)arg;
if (!f || f->sock == SOCKET_INVALID || !g_udp_ctx) return;
uint8_t buf[65536];
struct sockaddr_in from; socklen_t flen = sizeof(from);
ssize_t n = recvfrom(f->sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
if (n <= 0) return;
f->last_activity_tb = get_time_tb();
// Обернуть ответ: меняем src/dst для восстановления на стороне клиента
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + n);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = ETCP_ID_UDP_PROXY;
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
memcpy(e->dgram + 2, &f->client_node_id, 8);
// Ответ src = оригинальный dst_ip:dest_port
memcpy(e->dgram + 10, &f->dst_ip, 4);
memcpy(e->dgram + 14, &f->dst_port, 2);
// Ответ dst = оригинальный src_ip:src_port
memcpy(e->dgram + 16, &f->src_ip, 4);
memcpy(e->dgram + 20, &f->src_port, 2);
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, 0);
}
// ====================================================================
// Exit узел: принять REQUEST, создать сокет, переслать
// ====================================================================
static void exit_handle_data(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL);
if (!inst || !g_udp_ctx || entry->len < UDP_PROXY_HDR_SIZE + 1) goto drop;
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8);
uint32_t src_ip; memcpy(&src_ip, entry->dgram + 10, 4);
uint16_t src_port; memcpy(&src_port, entry->dgram + 14, 2);
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 16, 4);
uint16_t dst_port; memcpy(&dst_port, entry->dgram + 20, 2);
uint8_t* payload = entry->dgram + UDP_PROXY_HDR_SIZE;
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE;
struct udp_flow* f = flow_find(g_udp_ctx->flows, client_node_id, src_ip, src_port, dst_ip, dst_port);
if (!f) {
f = u_calloc(1, sizeof(struct udp_flow));
if (!f) goto drop;
f->client_node_id = client_node_id; f->src_ip = src_ip; f->src_port = src_port;
f->dst_ip = dst_ip; f->dst_port = dst_port;
f->ua = g_udp_ctx->ua; f->created_tb = get_time_tb(); f->last_activity_tb = f->created_tb;
f->sock = socket(AF_INET, SOCK_DGRAM, 0);
if (f->sock == SOCKET_INVALID) { u_free(f); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "udp_proxy: socket failed"); goto drop; }
socket_set_nonblocking(f->sock);
struct sockaddr_in bind_addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = INADDR_ANY}, .sin_port = 0};
bind(f->sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr));
f->read_id = uasync_add_socket(g_udp_ctx->ua, f->sock, flow_read_cb, NULL, NULL, f);
if (!f->read_id) { socket_close_wrapper(f->sock); u_free(f); goto drop; }
f->next = g_udp_ctx->flows; g_udp_ctx->flows = f; g_udp_ctx->flow_count++;
if (!g_udp_ctx->expire_timer)
g_udp_ctx->expire_timer = uasync_set_timeout(g_udp_ctx->ua, g_udp_ctx->flow_timeout_tb, g_udp_ctx, flow_expire_timer_cb, "udp_expire");
}
f->last_activity_tb = get_time_tb();
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}, .sin_port = dst_port};
sendto(f->sock, payload, payload_len, 0, (struct sockaddr*)&addr, sizeof(addr));
drop:
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: принять REPLY, доставить в TUN
// ====================================================================
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
// svc_id уже обработан диспетчером etcp_router
uint32_t src_ip, dst_ip;
uint16_t src_port, dst_port;
uint8_t* payload = entry->dgram + 1 + 1 + 8 + 4 + 2 + 4 + 2; // svc_id + subcmd + node_id + src_ip:port + dst_ip:port
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE;
memcpy(&dst_ip, entry->dgram + 1 + 1 + 8 + 4 + 2, 4); // src в сообщении → dst в ответе
memcpy(&dst_port, entry->dgram + 1 + 1 + 8 + 4 + 2 + 4, 2);
memcpy(&src_ip, entry->dgram + 1 + 1 + 8, 4); // dst в сообщении → src в ответе
memcpy(&src_port, entry->dgram + 1 + 1 + 8 + 4, 2);
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL);
udp_proxy_deliver_reply(inst, src_ip, src_port, dst_ip, dst_port, payload, payload_len);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Единый etcp_router коллбэк
// ====================================================================
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < 2) {
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
if (subcmd == UDP_PROXY_SUBCMD_DATA) {
if (g_udp_ctx && g_udp_ctx->is_exit) exit_handle_data(conn, entry);
else client_handle_reply(conn, entry);
return;
}
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Сторона клиента: отправить UDP датаграмму в exit узел
// ====================================================================
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
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 || !g_udp_ctx) return -1;
struct ll_entry* e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_UDP_PROXY;
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
memcpy(e->dgram + 2, &inst->node_id, 8);
memcpy(e->dgram + 10, &src_ip, 4);
memcpy(e->dgram + 14, &src_port, 2);
memcpy(e->dgram + 16, &dst_ip, 4);
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, 0);
}
// ====================================================================
// Сторона клиента: доставить UDP ответ в TUN
// ====================================================================
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_client || !inst->tcp_proxy_client->tun) return -1;
// Собрать IP/UDP ответный пакет
size_t ip_len = 20 + 8 + payload_len;
uint8_t* pkt = u_malloc(ip_len);
if (!pkt) return -1;
memset(pkt, 0, 20);
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_UDP;
memcpy(pkt + 12, &src_ip, 4); memcpy(pkt + 16, &dst_ip, 4);
uint16_t total_len = htons(20 + 8 + payload_len);
memcpy(pkt + 2, &total_len, 2);
{ uint32_t ip_sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); ip_sum += w; }
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16); ip_sum += (ip_sum >> 16);
uint16_t cs = (uint16_t)(~ip_sum); memcpy(pkt + 10, &cs, 2); }
uint16_t udp_len = htons(8 + payload_len);
memcpy(pkt + 24, &udp_len, 2);
memcpy(pkt + 20, &src_port, 2);
memcpy(pkt + 22, &dst_port, 2);
if (payload_len > 0) memcpy(pkt + 28, payload, payload_len);
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(pkt); return -1; }
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_client->tun->input_queue, e);
return 0;
}
static void flow_expire_timer_cb(void* arg) {
struct udp_proxy_ctx* ctx = (struct udp_proxy_ctx*)arg;
flow_expire(ctx);
ctx->expire_timer = NULL;
if (ctx->flows)
ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->flow_timeout_tb, ctx, flow_expire_timer_cb, "udp_expire");
}
static void flow_expire(struct udp_proxy_ctx* ctx) {
uint64_t now = get_time_tb(); struct udp_flow** prev = &ctx->flows;
while (*prev) {
struct udp_flow* f = *prev;
if (now - f->last_activity_tb > ctx->flow_timeout_tb) {
*prev = f->next; ctx->flow_count--;
if (f->read_id) { uasync_remove_socket_t(f->ua, f->sock); f->read_id = NULL; }
socket_close_wrapper(f->sock); u_free(f);
} else prev = &f->next;
}
}
// ====================================================================
// Публичные init/destroy
// ====================================================================
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
if (!inst) return -1;
struct udp_proxy_ctx* ctx = u_calloc(1, sizeof(struct udp_proxy_ctx));
if (!ctx) return -1;
ctx->inst = inst; ctx->ua = ua;
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
ctx->expire_timer = NULL;
ctx->is_exit = inst->tcp_proxy_server.enabled;
g_udp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy initialized (exit=%d)", ctx->is_exit);
return 0;
}
void udp_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !g_udp_ctx) return;
etcp_router_unbind(inst, ETCP_ID_UDP_PROXY);
if (g_udp_ctx->expire_timer) { uasync_cancel_timeout(g_udp_ctx->ua, g_udp_ctx->expire_timer); g_udp_ctx->expire_timer = NULL; }
struct udp_flow* f = g_udp_ctx->flows;
while (f) { struct udp_flow* n = f->next;
if (f->read_id) { uasync_remove_socket_t(g_udp_ctx->ua, f->sock); f->read_id = NULL; }
socket_close_wrapper(f->sock); u_free(f); f = n; }
u_free(g_udp_ctx); g_udp_ctx = NULL;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy destroyed");
}
+65
View File
@@ -0,0 +1,65 @@
// udp_proxy.h — UDP датаграммный прокси: клиент ↔ exit через etcp_router
#ifndef UDP_PROXY_H
#define UDP_PROXY_H
#include <stdint.h>
#include "../lib/socket_compat.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
// Подкоманды
#define UDP_PROXY_SUBCMD_DATA 0x01 // client→exit: пробрось датаграмму | exit→client: ответ
// Заголовок сообщения (не включая байт svc_id)
// svc_id(1) + subcmd(1) + sender_node_id(8) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload
#define UDP_PROXY_HDR_SIZE 22
// Один UDP поток (сторона exit узла: сопоставляет client_node_id + кортеж адресов с OS сокетом)
struct udp_flow {
struct udp_flow* next;
uint64_t client_node_id;
uint32_t src_ip;
uint16_t src_port;
uint32_t dst_ip;
uint16_t dst_port;
socket_t sock;
void* read_id;
struct UASYNC* ua;
uint64_t created_tb;
uint64_t last_activity_tb;
};
struct udp_proxy_ctx {
int initialized;
int is_exit; // 1=exit узел (создаёт сокеты), 0=клиент (шлёт через etcp)
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
struct udp_flow* flows;
int flow_count;
uint64_t flow_timeout_tb;
void* expire_timer; // периодический таймер очистки истёкших потоков
};
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua);
void udp_proxy_destroy(struct UTUN_INSTANCE* inst);
// Exit узел: принимает REQUEST, создаёт сокет, шлёт данные
// Сторона клиента: принимает REPLY, доставляет на TUN
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
// Сторона клиента: принять IP/UDP пакет с TUN, отправить через etcp_route_send
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
uint32_t src_ip, uint16_t src_port,
uint32_t dst_ip, uint16_t dst_port,
const uint8_t* payload, size_t payload_len);
// Сторона клиента: доставить ответ на TUN (вызывается из recv_cb)
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);
#endif // UDP_PROXY_H
+111
View File
@@ -0,0 +1,111 @@
#include <string.h>
#include <stdint.h>
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/radix.h"
#include "route_node.h"
#include "route6_lib.h"
#define HASH_SIZE 256
#define RADIX_OFF 1 // skip first byte (length) in radix key comparisons
static void make_key6(uint8_t out[ROUTE6_KEY_SIZE], const uint8_t addr[16]) {
out[0] = ROUTE6_KEY_SIZE;
memcpy(out + 1, addr, 16);
}
static void prefix_to_mask6(uint8_t plen, uint8_t mask[16]) {
memset(mask, 0, 16);
if (plen == 0) return;
for (int i = 0; i < 16 && plen > 0; i++) {
if (plen >= 8) { mask[i] = 0xFF; plen -= 8; }
else { mask[i] = (uint8_t)(0xFF00 >> plen); plen = 0; }
}
}
static void make_mask6(uint8_t out[ROUTE6_KEY_SIZE], uint8_t plen) {
out[0] = ROUTE6_KEY_SIZE;
prefix_to_mask6(plen, out + 1);
}
struct ROUTE6_TABLE *route6_table_create(struct UASYNC *ua) {
struct ROUTE6_TABLE *t = u_calloc(1, sizeof(struct ROUTE6_TABLE));
if (!t) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: alloc failed");
return NULL;
}
if (rn_inithead((void**)&t->rnh, RADIX_OFF) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: rn_inithead failed");
u_free(t);
return NULL;
}
t->queue = queue_new(ua, HASH_SIZE, ROUTE6_NODE_ID_OFF, sizeof(uint64_t), "route6");
if (!t->queue) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_table_create: queue_new failed");
rn_detachhead((void**)&t->rnh);
u_free(t);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route6_table_create: table=%p", (void*)t);
return t;
}
void route6_table_destroy(struct ROUTE6_TABLE *table) {
if (!table) return;
struct ll_entry *entry;
while ((entry = queue_data_get(table->queue))) {
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
rn_delete(rd->key, rd->mask, &table->rnh->rh);
queue_entry_free(entry);
}
queue_free(table->queue);
rn_detachhead((void**)&table->rnh);
u_free(table);
}
bool route6_insert(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return false;
const struct NODEINFO_IPV6_SUBNET *subnets = NULL;
int cnt = get_node_v6_routes(node, &subnets);
if (cnt <= 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route6_insert: node has no v6 subnets");
return false;
}
for (int i = 0; i < cnt; i++) {
struct ll_entry *entry = queue_entry_new(ROUTE6_DATA_SIZE);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route6_insert: queue_entry_new failed"); return false; }
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
make_key6(rd->key, subnets[i].addr);
make_mask6(rd->mask, subnets[i].prefix_length);
rd->v_node_info = node;
rd->node_id = node->node.node_id;
rd->prefix_length = subnets[i].prefix_length;
struct radix_node *rn = rn_addroute(rd->key, rd->mask, &table->rnh->rh, rd->rn_nodes);
if (!rn) { queue_entry_free(entry); continue; }
queue_data_put_with_index(table->queue, entry);
}
return true;
}
void route6_delete(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return;
uint64_t node_id = node->node.node_id;
struct ll_entry *entry = queue_find_data_by_index(table->queue, &node_id);
while (entry) {
struct ROUTE6_DATA *rd = (struct ROUTE6_DATA *)entry->data;
rn_delete(rd->key, rd->mask, &table->rnh->rh);
struct ll_entry *next = queue_find_next_by_index(table->queue, &node_id, entry);
queue_remove_data(table->queue, entry);
queue_entry_free(entry);
entry = next;
}
}
const struct ROUTE6_DATA *route6_lookup(struct ROUTE6_TABLE *table, const uint8_t addr[16]) {
if (!table) return NULL;
uint8_t key[ROUTE6_KEY_SIZE];
make_key6(key, addr);
struct radix_node *leaf = rn_match(key, &table->rnh->rh);
if (!leaf || (leaf->rn_flags & RNF_ROOT)) return NULL;
return (const struct ROUTE6_DATA *)((const char *)leaf - offsetof(struct ROUTE6_DATA, rn_nodes));
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef ROUTE6_LIB_H
#define ROUTE6_LIB_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "../lib/radix.h"
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
// таблица маршрутизации IPv6 на radix tree + ll_queue (поиск по node_id для удаления)
struct ROUTE6_DATA {
struct radix_node rn_nodes[2]; // scratch-буфер для rn_addroute
struct NODEINFO_Q *v_node_info; // узел-владелец
uint64_t node_id; // ключ для hash-поиска в ll_queue
uint8_t prefix_length; // 0..128
uint8_t key[17]; // sockaddr-like: {len=17, addr[16]}
uint8_t mask[17]; // sockaddr-like: {len=17, mask[16]}
};
#define ROUTE6_DATA_SIZE sizeof(struct ROUTE6_DATA)
#define ROUTE6_NODE_ID_OFF offsetof(struct ROUTE6_DATA, node_id)
#define ROUTE6_KEY_SIZE 17
struct ROUTE6_TABLE {
struct radix_node_head *rnh; // radix tree
struct ll_queue *queue; // indexed by node_id for delete
};
struct ROUTE6_TABLE *route6_table_create(struct UASYNC *ua);
void route6_table_destroy(struct ROUTE6_TABLE *table);
bool route6_insert(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node);
void route6_delete(struct ROUTE6_TABLE *table, struct NODEINFO_Q *node);
const struct ROUTE6_DATA *route6_lookup(struct ROUTE6_TABLE *table, const uint8_t addr[16]);
#endif
+696 -187
View File
File diff suppressed because it is too large. Load diff
+83 -5
View File
@@ -16,6 +16,8 @@
#define ROUTE_SUBCMD_NODEINFO 0x04 // полная информация об узле + маршруты
#define ROUTE_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы
#define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
#define ROUTE_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента
#define ROUTE_SUBCMD_NAT_CHECK_REQ 0x0A // запрос от клиента на проверку NAT для сокета
#define MAX_HOPS 16
#define BGP_NODES_HASH_SIZE 256
@@ -52,11 +54,40 @@ struct ROUTE_BGP_CONN_ITEM {
struct ETCP_CONN* conn;
};
/**
* @brief Пакет NAT_INFO (фиксированный)
*/
struct BGP_NAT_INFO {
uint8_t cmd;
uint8_t subcmd;
uint8_t socket_id; // MY socket id (link->conn->sock_id)
uint8_t nat_ip[4]; // network byte order
uint16_t nat_port; // network byte order
uint8_t nat_type; // NAT_TYPE_*
} __attribute__((packed));
/**
* @brief Пакет запроса на проверку NAT от клиента к серверу
*/
struct BGP_NAT_CHECK_REQ {
uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_NAT_CHECK_REQ
uint8_t socket_id; // ID сокета клиента, который надо проверить
uint32_t interface_ip; // interface_addr IP клиента (network byte order)
uint16_t interface_port; // interface_addr port клиента (network byte order)
} __attribute__((packed));
struct route_ping_pending;
struct ETCP_LINK;
struct ROUTE_BGP {
struct UTUN_INSTANCE* instance;
struct ll_queue* senders_list;
struct ll_queue* nodes;
struct NODEINFO_Q* local_node;
struct route_ping_pending* ping_pending;
uint32_t next_ping_req_id;
uint8_t allow_nat_check_local; // 1 = разрешить NAT check для локальных подсетей (для тестов)
};
/**
@@ -115,11 +146,6 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
*/
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
/**
* @brief Рассылает local_node NODEINFO всем senders_list кроме exclude.
*/
void route_bgp_broadcast_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude);
/**
* @brief Отправляет WITHDRAW для node_id (вызывает broadcast_withdraw).
*/
@@ -132,6 +158,17 @@ void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск оптимального ETCP соединения для указанного node_id.
*
* Перебирает paths узла, выбирает путь с минимальным hop_count.
*
* @param bgp указатель на ROUTE_BGP
* @param node_id целевой узел
* @return оптимальный ETCP_CONN* или NULL
*/
struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Добавляет путь (conn) в paths узла.
*
@@ -146,4 +183,45 @@ int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t*
*/
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn);
/**
* @brief Отправляет NAT_INFO клиенту с его типом NAT.
*
* @param conn соединение к клиенту
* @param nat_ip IP клиента (network byte order)
* @param nat_port порт клиента (network byte order)
* @param nat_type NAT_TYPE_EIM или NAT_TYPE_STRICT
*/
void route_bgp_send_nat_info(struct ETCP_CONN* conn, uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type);
/**
* @brief Отправляет запрос на проверку NAT для указанного socket_id серверу.
*
* @param conn соединение к серверу
* @param socket_id ID сокета для проверки
*/
void route_bgp_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id);
/**
* @brief Разрешить/запретить NAT check для локальных подсетей (127.0.0.1, 10.x.x.x и т.д.)
* По умолчанию запрещено. Полезно для тестов.
*/
void route_bgp_set_nat_check_local(struct ROUTE_BGP* bgp, int allow);
/**
* @brief Запускает NAT check для всех линков всех соединений.
*/
void route_bgp_request_nat_check_all(struct ROUTE_BGP* bgp);
/**
* @brief Запускает NAT check для одного линка.
*/
void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link);
/* Аргумент callback NAT check */
struct nat_check_arg {
struct ETCP_LINK* link;
uint32_t nat_ip;
uint16_t nat_port;
};
#endif // ROUTE_BGP_H
+16
View File
@@ -24,3 +24,19 @@ bgp_update local nodelist:
bgp_withdraw(struct ROUTE_BGP bgp, uint64_t node_to_del, uint64_t wd_source) - wd_source это узел который захотел withdraw.
- находим у себя node to del. удаляем если в hoplist найден wd_node (или мы = wd_node)
если удалили - распространяем по всем линкам с этими же аргументами
===============================
определение nat
определяем что оба узла в локальной сети:
-если обе ноды имеют локальные адреса сокетов пробуем пинг по локальным адресам
если узел подключился и его сокетовый ip:port отличается от connection ip:port - устанавливаем флаг nat. и инициируем проверку пингом третьим узлом:
проверка считается успешной если по результату пинга ip источника пинга отличается от ip запросившего пинг узла.
проверка типа nat узла который подключился:
- пингуем узел с целью зафиксировать свой ip (в сокете может быть например 0.0.0.0)
- даём команду третьему узлу пингануть этот узел.
- по результатам двух пингов определяем тип nat
+446
View File
@@ -0,0 +1,446 @@
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "route_node.h"
#include "route_bgp.h"
#include "route_connectivity.h"
#define CONN_MAX_SOCKET_CANDIDATES 8
struct conn_probe_ctx {
struct UTUN_INSTANCE* instance;
struct NODEINFO_Q* nq;
uint8_t addr_type; // ADDR_TYPE_*
struct sockaddr_storage target_addr;
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
struct ETCP_SOCKET* candidate_sockets[CONN_MAX_SOCKET_CANDIDATES];
uint8_t candidate_count;
uint8_t candidate_index;
uint16_t best_across_sockets; // min RTT по всем сокетам
uint8_t count_total; // 3 на серию
uint8_t count_sent;
uint8_t count_ok;
uint16_t min_rtt; // min RTT в текущей серии
uint16_t timeout_ms;
void* ping_timer;
};
// ---- forward ----
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len);
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok);
static void conn_probe_start_series(struct conn_probe_ctx* ctx);
// ---- helpers ----
static int sock_addr_cmp(const struct sockaddr_storage* a, const struct sockaddr_storage* b) {
if (a->ss_family != b->ss_family) return 1;
if (a->ss_family == AF_INET) {
const struct sockaddr_in* sa = (const struct sockaddr_in*)a;
const struct sockaddr_in* sb = (const struct sockaddr_in*)b;
if (sa->sin_addr.s_addr != sb->sin_addr.s_addr) return 1;
if (sa->sin_port != sb->sin_port) return 1;
return 0;
}
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6* sa = (const struct sockaddr_in6*)a;
const struct sockaddr_in6* sb = (const struct sockaddr_in6*)b;
if (memcmp(&sa->sin6_addr, &sb->sin6_addr, 16) != 0) return 1;
if (sa->sin6_port != sb->sin6_port) return 1;
return 0;
}
return memcmp(a, b, sizeof(struct sockaddr_storage));
}
// проверяет что два адреса (NODEINFO_IPV4_ADDR) не дубликаты по IP+port
static int addr_eq(const struct NODEINFO_IPV4_ADDR* a, uint32_t ip, uint16_t port) {
uint32_t a_ip; memcpy(&a_ip, a->addr, 4);
return a_ip == ip && a->port == port;
}
// собирает список локальных сокетов-кандидатов для probing заданного адреса
// сортирует: лучшие (по совпадению подсети / типу NAT) первые
static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
uint8_t addr_type,
const struct sockaddr_storage* target_addr,
struct ETCP_SOCKET** out_sockets, uint8_t max_count) {
if (!instance || !target_addr || !out_sockets || max_count == 0) return 0;
int target_family = target_addr->ss_family;
int found = 0;
struct ETCP_SOCKET* e_sock;
if (target_family == AF_INET6) {
struct sockaddr_in6* target_sin6 = (struct sockaddr_in6*)target_addr;
// IPv6: prefer sockets in same /64 subnet
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family == AF_INET6) {
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
if (memcmp(&if_sin6->sin6_addr, &target_sin6->sin6_addr, 8) == 0) {
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
}
}
e_sock = e_sock->next;
}
// Pass 2: all other IPv6 sockets
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET6) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
e_sock = e_sock->next;
}
return found;
}
// IPv4 target — existing logic
uint32_t target_ip = ((const struct sockaddr_in*)target_addr)->sin_addr.s_addr;
e_sock = instance->etcp_sockets;
// Проход 1: точное совпадение подсети (для INTERFACE) или PUBLIC/NAT_VERIFIED (для NAT/REAL)
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int match = 0;
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
uint32_t if_ip = if_sin->sin_addr.s_addr;
if (addr_type == ADDR_TYPE_INTERFACE) {
// предпочитаем PRIVATE сокеты в той же /24 подсети
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE && (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
match = 1;
} else {
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC
if (e_sock->nat_type == NAT_VERIFIED_DIRECT) match = 1;
else if (e_sock->nat_type == NAT_VERIFIED_EIM) match = 1;
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC) match = 1;
}
if (match) {
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
}
e_sock = e_sock->next;
}
// Проход 2: все остальные подходящие
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) {
if (addr_type == ADDR_TYPE_INTERFACE) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
} else {
// любой не-private
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
}
}
e_sock = e_sock->next;
}
// Проход 3: остальные IPv4 (fallback)
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) out_sockets[found++] = e_sock;
e_sock = e_sock->next;
}
return found;
}
// ---- probe lifecycle ----
static void conn_probe_start_series(struct conn_probe_ctx* ctx) {
if (ctx->candidate_index >= ctx->candidate_count) {
// все сокеты перебраны
if (ctx->best_across_sockets != 65535) conn_probe_finish(ctx, 1);
else conn_probe_finish(ctx, 0);
return;
}
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
ctx->count_sent = 0;
ctx->count_ok = 0;
ctx->min_rtt = 65535;
static const char* atype_names[] = {"INTERFACE", "NAT", "REAL"};
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe series start: socket=%s target=%s:%u type=%s",
sock->name,
ip_to_str(&((struct sockaddr_in*)&ctx->target_addr)->sin_addr, AF_INET).str,
(unsigned)ntohs(((struct sockaddr_in*)&ctx->target_addr)->sin_port),
atype_names[ctx->addr_type]);
int ret = etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
&ctx->target_addr, ctx->timeout_ms,
conn_probe_single_cb, ctx, NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot start ping from socket %s", sock->name);
ctx->count_sent = 3; // simulate full failure
ctx->candidate_index++;
conn_probe_start_series(ctx);
}
}
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len) {
(void)nonce; (void)resp_data; (void)resp_data_len;
struct conn_probe_ctx* ctx = (struct conn_probe_ctx*)arg;
if (!ctx) return;
ctx->count_sent++;
if (success) {
ctx->count_ok++;
if (rtt < ctx->min_rtt) ctx->min_rtt = rtt;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe ping: ok=%d rtt=%u sent=%d/%d socket=%s",
success, rtt, ctx->count_sent, ctx->count_total,
ctx->candidate_sockets[ctx->candidate_index]->name);
if (ctx->count_sent < ctx->count_total) {
// продолжаем с тем же сокетом
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
int ret = etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
&ctx->target_addr, ctx->timeout_ms,
conn_probe_single_cb, ctx, NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot continue ping from socket %s", sock->name);
ctx->count_sent = ctx->count_total; // force finish series
ctx->count_ok = 0;
} else return; // следующий пинг отправлен, ждём callback
}
// серия из 3 пингов завершена
if (ctx->count_ok > 0) {
if (ctx->min_rtt < ctx->best_across_sockets) ctx->best_across_sockets = ctx->min_rtt;
conn_probe_finish(ctx, 1);
} else {
// этот сокет не подошёл — пробуем следующий
ctx->candidate_index++;
conn_probe_start_series(ctx);
}
}
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok) {
if (!ctx || !ctx->nq) return;
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
switch (ctx->addr_type) {
case ADDR_TYPE_INTERFACE:
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
c->interface_probe_time = get_time_tb();
break;
case ADDR_TYPE_NAT:
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
c->nat_probe_time = get_time_tb();
break;
case ADDR_TYPE_REAL:
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
c->real_probe_time = get_time_tb();
break;
}
if (c->pending_count > 0) c->pending_count--;
if (c->pending_count == 0) {
c->probe_status = PROBE_STATUS_DONE;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
(unsigned long long)ctx->nq->node.node_id,
c->interface_status, c->nat_status, c->real_status);
}
u_free(ctx);
}
// ---- public API ----
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* nq) {
if (!instance || !nq) return;
if (nq->node.node_id == instance->node_id) return; // не пингуем себя
if (nq->connectivity.probe_status == PROBE_STATUS_IN_PROGRESS) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe already in progress for node %016llx", (unsigned long long)nq->node.node_id);
return;
}
nq->connectivity.probe_status = PROBE_STATUS_IN_PROGRESS;
nq->connectivity.probe_start_time = get_time_tb();
nq->connectivity.interface_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.nat_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.real_status = PROBE_RESULT_UNKNOWN;
nq->connectivity.interface_min_rtt = 65535;
nq->connectivity.nat_min_rtt = 65535;
nq->connectivity.real_min_rtt = 65535;
nq->connectivity.pending_count = 0;
int pend = 0;
// ---- IPv4 addresses ----
const struct NODEINFO_IPV4_ADDR* addrs;
int addr_count = get_node_v4_addrs(nq, &addrs);
if (addr_count > 0) {
uint32_t seen_ips[16]; uint16_t seen_ports[16];
int seen_count = 0;
for (int i = 0; i < addr_count; i++) {
uint32_t ip; memcpy(&ip, addrs[i].addr, 4);
uint16_t port = addrs[i].port;
if (ip == 0 || port == 0) continue;
int dup = 0;
for (int j = 0; j < seen_count; j++) {
if (seen_ips[j] == ip && seen_ports[j] == port) { dup = 1; break; }
}
if (dup) continue;
if (seen_count >= 16) break;
seen_ips[seen_count] = ip; seen_ports[seen_count] = port; seen_count++;
struct sockaddr_storage target;
memset(&target, 0, sizeof(target));
struct sockaddr_in* sin = (struct sockaddr_in*)&target;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = ip;
sin->sin_port = htons(port);
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, addrs[i].type, &target,
candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for target %s:%u type=%d",
ip_to_str(&ip, AF_INET).str, port, addrs[i].type);
continue;
}
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
if (!ctx) continue;
ctx->instance = instance;
ctx->nq = nq;
ctx->addr_type = addrs[i].type;
ctx->target_addr = target;
memcpy(ctx->peer_pubkey, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count;
ctx->candidate_index = 0;
ctx->count_total = CONN_PROBE_COUNT;
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
ctx->best_across_sockets = 65535;
pend++;
conn_probe_start_series(ctx);
}
}
// ---- IPv6 addresses ----
const struct NODEINFO_IPV6_ADDR* addrs6;
int addr6_count = get_node_v6_addrs(nq, &addrs6);
if (addr6_count > 0) {
uint8_t seen_ip6s[16][16]; uint16_t seen_ports6[16];
int seen6_count = 0;
for (int i = 0; i < addr6_count; i++) {
if (addrs6[i].port == 0) continue;
// skip zero address
int zero = 1;
for (int z = 0; z < 16; z++) if (addrs6[i].addr[z] != 0) { zero = 0; break; }
if (zero) continue;
int dup = 0;
for (int j = 0; j < seen6_count; j++) {
if (memcmp(seen_ip6s[j], addrs6[i].addr, 16) == 0 && seen_ports6[j] == addrs6[i].port) { dup = 1; break; }
}
if (dup) continue;
if (seen6_count >= 16) break;
memcpy(seen_ip6s[seen6_count], addrs6[i].addr, 16);
seen_ports6[seen6_count] = addrs6[i].port;
seen6_count++;
struct sockaddr_storage target;
memset(&target, 0, sizeof(target));
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&target;
sin6->sin6_family = AF_INET6;
memcpy(&sin6->sin6_addr, addrs6[i].addr, 16);
sin6->sin6_port = htons(addrs6[i].port);
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, addrs6[i].type, &target,
candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for IPv6 target type=%d", addrs6[i].type);
continue;
}
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
if (!ctx) continue;
ctx->instance = instance;
ctx->nq = nq;
ctx->addr_type = addrs6[i].type;
ctx->target_addr = target;
memcpy(ctx->peer_pubkey, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count;
ctx->candidate_index = 0;
ctx->count_total = CONN_PROBE_COUNT;
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
ctx->best_across_sockets = 65535;
pend++;
conn_probe_start_series(ctx);
}
}
if (pend == 0) {
nq->connectivity.probe_status = PROBE_STATUS_DONE;
if (addr_count == 0 && addr6_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "no addresses to probe for node %016llx", (unsigned long long)nq->node.node_id);
}
} else {
nq->connectivity.pending_count = pend;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe started for node %016llx: %d series pending",
(unsigned long long)nq->node.node_id, pend);
}
}
void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* nq) {
if (!instance || !nq) return;
nq->connectivity.probe_status = PROBE_STATUS_NONE;
nq->connectivity.pending_count = 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe cancelled for node %016llx", (unsigned long long)nq->node.node_id);
}
void route_connectivity_cancel_all(struct UTUN_INSTANCE* instance) {
if (!instance || !instance->bgp || !instance->bgp->nodes) return;
int count = 0;
struct ll_entry* e = instance->bgp->nodes->head;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
nq->connectivity.probe_status = PROBE_STATUS_NONE;
nq->connectivity.pending_count = 0;
e = e->next;
count++;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe cancel ALL: %d nodes", count);
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef ROUTE_CONNECTIVITY_H
#define ROUTE_CONNECTIVITY_H
#include <stdint.h>
#include "route_node.h"
struct UTUN_INSTANCE;
// Запускает зондирование связности ко всем адресам удалённого узла
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance,
struct NODEINFO_Q* nq);
// Отменяет все pending пробы для узла (при удалении / withdraw)
void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance,
struct NODEINFO_Q* nq);
// Отменяет все pending пробы для всех узлов (при destroy)
void route_connectivity_cancel_all(struct UTUN_INSTANCE* instance);
#endif // ROUTE_CONNECTIVITY_H
+38 -16
View File
@@ -1,12 +1,13 @@
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "utun_instance.h"
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#define INITIAL_CAPACITY 100
@@ -167,7 +168,8 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
// === Проверка пересечений ===
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4); // addr уже в network byte order (big-endian)
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix = subnets[i].prefix_length;
if (check_route_overlap_in_table(network, prefix,
@@ -195,6 +197,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix_length = subnets[i].prefix_length;
int pos = binary_search_insert_pos(table->entries, table->count, network, prefix_length);
@@ -207,33 +210,33 @@ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
struct ROUTE_ENTRY *e = &table->entries[pos];
e->network = network;
e->prefix_length = prefix_length;
e->v_node_info = node; // все префиксы узла ссылаются на один объект
e->v_node_info = node;
table->count++;
table->stats.learned_routes++;
if (node && node->node.hop_count == 0) table->stats.local_routes++; else table->stats.learned_routes++;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node_info=%p",
count, (void*)node);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node=%016llx",
count, node ? (unsigned long long)node->node.node_id : 0ULL);
return true;
}
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node_info=%p", (void*)node);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node=%016llx",
(unsigned long long)node->node.node_id);
size_t i = 0;
size_t removed = 0;
while (i < table->count) {
if (table->entries[i].v_node_info == node) {
// v_node_info — внешний, не освобождаем
if (i < table->count - 1) {
memmove(&table->entries[i], &table->entries[i + 1],
(table->count - i - 1) * sizeof(struct ROUTE_ENTRY));
}
table->count--;
table->stats.learned_routes--;
if (node && node->node.hop_count == 0) table->stats.local_routes--; else table->stats.learned_routes--;
removed++;
continue;
}
@@ -277,8 +280,7 @@ void route_table_print(const struct ROUTE_TABLE *table) {
const struct ROUTE_ENTRY *entry = &table->entries[i];
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d",
i + 1, ip_to_string(entry->network).a, entry->prefix_length);
if (entry->v_node_info) {
if (entry->v_node_info && entry->v_node_info->node.hop_count != 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " v_node_info=%p", (void*)entry->v_node_info);
} else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " LOCAL");
@@ -306,3 +308,23 @@ int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_leng
return 0;
}
int is_local_subnet(uint32_t ip) {
uint8_t* bip=(uint8_t*)&ip;
uint8_t first_octet = bip[0];
uint8_t second_octet = bip[1];
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "check ip %d.%d : ", first_octet, second_octet);
if (first_octet == 0) return 1;
if (first_octet == 10) return 1;
if (first_octet == 127) return 1;
if (first_octet == 169 && second_octet == 254) return 1;
if (first_octet >= 224 && first_octet <= 239) return 1;
if (first_octet == 172 && second_octet >= 16 && second_octet <= 31) return 1;
if (first_octet == 192 && second_octet == 168) return 1;
if (ip==0xffffffff) return 1;
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "result: global ip %d.%d : ", first_octet, second_octet);
return 0;
}
+10
View File
@@ -110,4 +110,14 @@ void route_table_print(const struct ROUTE_TABLE *table);
*/
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length);
bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
/**
* @brief Проверяет что IP адрес НЕ является публичным (роутится в интернете)
*
* @param ip IP адрес в сетевом порядке (big-endian)
* @return 0 - публичный (роутится в интернете), 1 - локальный/мультикастовый/зарезервированный
*/
int is_local_subnet(uint32_t ip);
#endif // ROUTE_LIB_H
+307 -128
View File
@@ -1,128 +1,307 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части BGP_NODEINFO_Q
* и возвращает прямой указатель на массив struct BGP_NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на BGP_NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) {
return -1;
}
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) {
return 0; // успех, но нет подсетей
}
// Начало динамических полей сразу после фиксированной части NODEINFO
const uint8_t *dynamic = (const uint8_t *)&node->node + sizeof(struct NODEINFO);
// 1. Пропускаем node_name
dynamic += info->node_name_len;
// 2. Пропускаем local_v4_sockets
dynamic += info->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
// 3. Пропускаем local_v6_sockets
dynamic += info->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
// Теперь dynamic указывает точно на начало массива NODEINFO_IPV4_SUBNET
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "get_node_routes: returned %u IPv4 subnets from node %p",
(unsigned)info->local_v4_subnets, (void*)node);
return (int)info->local_v4_subnets;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
return -1;
}
if (!bgp->local_node) {
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t d = vc * sizeof(struct NODEINFO_IPV4_SUBNET);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + d);
if (!bgp->local_node) return -1;
bgp->local_node->node.node_id = htobe64(instance->node_id);
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = 1;
bgp->local_node->node.local_v4_subnets = vc;
}
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
int changed = (vc != (int)bgp->local_node->node.local_v4_subnets);
if (!changed && vc > 0) {
uint8_t* current = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)current;
s = instance->config->my_subnets;
bool same = true;
while (s) {
if (s->ip.family == AF_INET) {
if (memcmp(ra->addr, &s->ip.addr.v4, 4) != 0 || ra->prefix_length != s->netmask) {
same = false;
break;
}
ra++;
}
s = s->next;
}
changed = !same;
}
if (changed) {
uint8_t oldv = bgp->local_node->node.ver;
bgp->local_node->node.ver = ((oldv + 1) % 255) + 1;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) {
memcpy(ra->addr, &s->ip.addr.v4, 4);
ra->prefix_length = s->netmask;
ra++;
}
s = s->next;
}
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
static const uint8_t* node_dyn_start(const struct NODEINFO* info) {
return (const uint8_t*)info + sizeof(struct NODEINFO);
}
static const uint8_t* skip_name(const uint8_t* p, uint8_t name_len) {
return p + name_len;
}
static const uint8_t* skip_v4_sockets_meta(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_SOCKET_META);
}
static const uint8_t* skip_v4_addrs(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_ADDR);
}
static const uint8_t* skip_v6_sockets_meta(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV6_SOCKET_META);
}
static const uint8_t* skip_v6_addrs(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV6_ADDR);
}
static const uint8_t* skip_v4_subnets(const uint8_t* p, uint8_t count) {
return p + count * sizeof(struct NODEINFO_IPV4_SUBNET);
}
int get_node_v4_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SOCKET_META **out_meta) {
if (!node || !out_meta) return -1;
*out_meta = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_sockets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
*out_meta = (const struct NODEINFO_IPV4_SOCKET_META *)dynamic;
return (int)info->local_v4_sockets;
}
int get_node_v4_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_ADDR **out_addrs) {
if (!node || !out_addrs) return -1;
*out_addrs = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_addrs == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
*out_addrs = (const struct NODEINFO_IPV4_ADDR *)dynamic;
return (int)info->local_v4_addrs;
}
int get_node_v6_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SOCKET_META **out_meta) {
if (!node || !out_meta) return -1;
*out_meta = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_sockets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
*out_meta = (const struct NODEINFO_IPV6_SOCKET_META *)dynamic;
return (int)info->local_v6_sockets;
}
int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR **out_addrs) {
if (!node || !out_addrs) return -1;
*out_addrs = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_addrs == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
*out_addrs = (const struct NODEINFO_IPV6_ADDR *)dynamic;
return (int)info->local_v6_addrs;
}
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) return -1;
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
dynamic = skip_v6_addrs(dynamic, info->local_v6_addrs);
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
return (int)info->local_v4_subnets;
}
int get_node_v6_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SUBNET **out_subnets) {
if (!node || !out_subnets) return -1;
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v6_subnets == 0) return 0;
const uint8_t *dynamic = node_dyn_start(info);
dynamic = skip_name(dynamic, info->node_name_len);
dynamic = skip_v4_sockets_meta(dynamic, info->local_v4_sockets);
dynamic = skip_v4_addrs(dynamic, info->local_v4_addrs);
dynamic = skip_v6_sockets_meta(dynamic, info->local_v6_sockets);
dynamic = skip_v6_addrs(dynamic, info->local_v6_addrs);
dynamic = skip_v4_subnets(dynamic, info->local_v4_subnets);
*out_subnets = (const struct NODEINFO_IPV6_SUBNET *)dynamic;
return (int)info->local_v6_subnets;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
return -1;
}
size_t name_len = 0;
if (instance->name[0]) {
name_len = strlen(instance->name);
if (name_len > 63) name_len = 63;
}
int vc = 0, vc6 = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) { if (s->ip.family == AF_INET) vc++; else if (s->ip.family == AF_INET6) vc6++; s = s->next; }
int sock_count = 0, addr_count = 0;
int sock6_count = 0, addr6_count = 0;
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET) {
sock_count++;
addr_count++; // INTERFACE
if (e_sock->nat_addr.ss_family == AF_INET) {
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
if (nat_sin->sin_addr.s_addr != 0) {
if (e_sock->nat_type != NAT_VERIFIED_STRICT) addr_count++;
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
} else if (e_sock->local_addr.ss_family == AF_INET6) {
sock6_count++;
addr6_count++; // INTERFACE only (no NAT for IPv6)
}
e_sock = e_sock->next;
}
size_t dyn = name_len + sock_count * sizeof(struct NODEINFO_IPV4_SOCKET_META)
+ addr_count * sizeof(struct NODEINFO_IPV4_ADDR)
+ sock6_count * sizeof(struct NODEINFO_IPV6_SOCKET_META)
+ addr6_count * sizeof(struct NODEINFO_IPV6_ADDR)
+ vc * sizeof(struct NODEINFO_IPV4_SUBNET)
+ vc6 * sizeof(struct NODEINFO_IPV6_SUBNET);
int changed = 1;
uint8_t old_ver = 0;
if (bgp->local_node) {
old_ver = bgp->local_node->node.ver;
changed = (vc != (int)bgp->local_node->node.local_v4_subnets)
|| (sock_count != (int)bgp->local_node->node.local_v4_sockets)
|| (addr_count != (int)bgp->local_node->node.local_v4_addrs)
|| (name_len != bgp->local_node->node.node_name_len)
|| (memcmp(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) != 0)
|| (vc6 != (int)bgp->local_node->node.local_v6_subnets)
|| (sock6_count != (int)bgp->local_node->node.local_v6_sockets)
|| (addr6_count != (int)bgp->local_node->node.local_v6_addrs);
}
if (changed) {
if (bgp->local_node) u_free(bgp->local_node);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn);
if (!bgp->local_node) return -1;
bgp->local_node->node.node_id = instance->node_id;
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = (old_ver % 255) + 1;
bgp->local_node->node.local_v4_sockets = sock_count;
bgp->local_node->node.local_v4_addrs = addr_count;
bgp->local_node->node.local_v6_sockets = sock6_count;
bgp->local_node->node.local_v6_addrs = addr6_count;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->node.local_v6_subnets = vc6;
bgp->local_node->node.node_name_len = name_len;
memcpy(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
if (name_len) { memcpy(dp, instance->name, name_len); dp += name_len; }
// Write IPv4 socket meta
struct NODEINFO_IPV4_SOCKET_META* meta = (struct NODEINFO_IPV4_SOCKET_META*)dp;
e_sock = instance->etcp_sockets;
int midx = 0;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET) {
meta[midx].id = e_sock->sock_id;
meta[midx].type = e_sock->nat_type;
midx++;
}
e_sock = e_sock->next;
}
dp += sock_count * sizeof(struct NODEINFO_IPV4_SOCKET_META);
// Write IPv4 addresses
struct NODEINFO_IPV4_ADDR* addrs = (struct NODEINFO_IPV4_ADDR*)dp;
int aidx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET) {
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
memcpy(addrs[aidx].addr, &if_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(if_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_INTERFACE;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
if (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0) {
if (e_sock->nat_type != NAT_VERIFIED_STRICT) {
memcpy(addrs[aidx].addr, &nat_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(nat_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_NAT;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) {
memcpy(addrs[aidx].addr, &if_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(if_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_REAL;
addrs[aidx].socket_id = e_sock->sock_id;
aidx++;
}
}
e_sock = e_sock->next;
}
dp += addr_count * sizeof(struct NODEINFO_IPV4_ADDR);
// Write IPv6 socket meta
struct NODEINFO_IPV6_SOCKET_META* meta6 = (struct NODEINFO_IPV6_SOCKET_META*)dp;
e_sock = instance->etcp_sockets;
int m6idx = 0;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET6) {
meta6[m6idx].id = e_sock->sock_id;
meta6[m6idx].type = e_sock->nat_type;
m6idx++;
}
e_sock = e_sock->next;
}
dp += sock6_count * sizeof(struct NODEINFO_IPV6_SOCKET_META);
// Write IPv6 addresses (INTERFACE only)
struct NODEINFO_IPV6_ADDR* addrs6 = (struct NODEINFO_IPV6_ADDR*)dp;
int a6idx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->local_addr.ss_family == AF_INET6) {
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
memcpy(addrs6[a6idx].addr, &if_sin6->sin6_addr, 16);
addrs6[a6idx].port = ntohs(if_sin6->sin6_port);
addrs6[a6idx].type = ADDR_TYPE_INTERFACE;
addrs6[a6idx].socket_id = e_sock->sock_id;
a6idx++;
}
e_sock = e_sock->next;
}
dp += addr6_count * sizeof(struct NODEINFO_IPV6_ADDR);
// Write IPv4 subnets
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) { memcpy(ra->addr, &s->ip.addr.v4, 4); ra->prefix_length = s->netmask; ra++; }
s = s->next;
}
dp += vc * sizeof(struct NODEINFO_IPV4_SUBNET);
// Write IPv6 subnets
struct NODEINFO_IPV6_SUBNET* ra6 = (struct NODEINFO_IPV6_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET6) { memcpy(ra6->addr, &s->ip.addr.v6, 16); ra6->prefix_length = s->netmask; ra6++; }
s = s->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4s=%d v4a=%d v6s=%d v6a=%d v4sub=%d v6sub=%d ver=%d",
sock_count, addr_count, sock6_count, addr6_count, vc, vc6, bgp->local_node->node.ver);
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more