Author SHA1 Message Date
Evgeny 80177c9b3e fix: test_conn_mgr flakiness — wait for BGP nodeinfo, not just INIT links
Root cause: test1 used lks() (INIT links up) but conn_mgr needs BGP NODEINFO.
Nodeinfo arrives after INIT, so test1 must wait for it.
Also made result volatile to prevent loop optimization.
2026-06-30 00:34:06 +03:00
Evgeny 8f13f0ec4d refactor: extract handle_ping, handle_pong, send_init_response, handle_init_response_client from read callback
Reduce etcp_connections_read_callback_socket from ~600 to ~300 lines.
All 4 helpers are static file-scope functions, no logic changes.
2026-06-30 00:16:34 +03:00
Evgeny 5420e414ce fix: DIRECT detection read srс_ipv4/src_port before INIT RESPONSE overwrites the buffer
Both req (INIT_REQUEST) and resp (INIT_RESPONSE) point to pkt->data.
resp->peer_port at offset 23-24 overwrites req->src_ipv4[0..1] at same offset.
Save src_ipv4/src_port before building the response.
2026-06-29 23:58:46 +03:00
Evgeny 69112ffde1 feat: NAT_DIRECT detection during INIT handshake + refactor init packet structs
- Extract ETCP_INIT_REQUEST_PKT / ETCP_INIT_RESPONSE_PKT named structs
- Replace anonymous structs and manual offset parsing with typed struct access
- Add src_ipv4/src_port fields (V2) to INIT REQUEST for DIRECT detection
- Server compares reported client addr with observed src; match + public IP → NAT_TYPE_DIRECT
- Skip STUN NAT check for DIRECT links
- route_bgp: handle NAT_TYPE_DIRECT → NAT_VERIFIED_DIRECT in handle_nat_info
2026-06-29 23:15:38 +03:00
Evgeny 89d675f15d fix: drain input_send_q and input_wait_ack at end of etcp_conn_reset to ensure inflight queues are cleared on remote node restart 2026-06-29 20:17:53 +03:00
Evgeny 914f252b12 fix: remove send_blocked_inflight — per-link inflight gate was blocking input_send_q and ACK send, causing bidirectional deadlock 2026-06-29 20:04:20 +03:00
Evgeny a7b20f268f feat: conn_mgr, dummynet block-addr, indirect routing, alien nodes, nodeinfo refactor 2026-06-29 20:01:40 +03:00
Evgeny c0207ad41d fix: move nodeinfo_dyn_size declaration to route_bgp.h 2026-06-25 09:47:27 +03:00
Evgeny 8051dfe4e5 zxing-cpp: strip to core library only (remove tests/wrappers/examples/docs); .gitignore: fix /core to allow zxing-cpp/core/ dir 2026-06-25 09:37:45 +03:00
Evgeny 22c24d135e chatgui: invite link dialogs, zxing-cpp QR code, crypto transport, channel improvements; msg_transport: chat protocol support 2026-06-25 09:29:44 +03:00
Evgeny d8c8c1c5b0 control_server: node subscription protocol with output queue, push notifications on routing changes 2026-06-25 09:28:07 +03:00
Evgeny 231081dd2f docs: add chatgui animated emoji system and tdesktop-dev reference to AGENTS.md 2026-06-24 13:50:10 +03:00
Evgeny 62b3695a77 chatgui: rlottie animated emoji with hover-to-animate, drag-to-select
- Add LottieIcon class (TGS gzip decompress + rlottie C API render)
- Add AnimTimer singleton (33ms tick, activate/deactivate refcount)
- Add EmojiButton (hover-triggered animation in emoji picker)
- Add AnimatedEmoji support to EmojiData (5 TGS mappings)
- MessageDelegate: QTextLayout-based animated emoji overlay (text + reactions)
- ChatView: drag-to-select (click deselects, 5px drag starts selection)
- ChatView: hoveredIndexChanged signal for hover-triggered animations
- MessageList: wire AnimTimer tick to viewport update, hover → activate
- CMake: AUTORCC, rlottie, zlib dependencies
- Add 5 TGS animations as Qt resources
2026-06-24 13:45:25 +03:00
Evgeny 0e2b0e28d8 tools/chatgui: Makefile cmake wrapper, author name in bubble, emoji panel height ~1/1.7 2026-06-23 14:39:09 +03:00
Evgeny e50ca9978e tools/chatgui: input bar with emoji panel, custom tab bar, Ctrl+Enter multiline, scroll fixes 2026-06-23 00:17:07 +03:00
Evgeny b667d6f352 tools/chatgui: Qt5 GUI chat app stub with tray, channel list, message bubbles 2026-06-22 22:48:27 +03:00
Evgeny 6717c863e7 etcp_router: Ed25519 digital signature for service messages
- Add ed25519_public_key[32] to NODEINFO struct for pubkey distribution
- Add ed25519_public_key[32] to ROUTE_BGP (derived from X25519 privkey at init)
- Add ROUTER_FLAG_SIGNED (0x08) to SVC_ROUTE_HDR flags byte
- router_send_one_flags: when is_signed, sign [hdr][payload] and append 64-byte sig
- etcp_router_recv_cb: verify Ed25519 signature when ROUTER_FLAG_SIGNED set
- New API: etcp_router_conn_send_signed()
- Drop signed packets if sender node not in routing table or no Ed25519 key
- 5 unit tests in test_etcp_router_unit.c covering OK/tampered/unknown/no-key/short
2026-06-22 15:53:24 +03:00
Evgeny 2bbdf8195f fix: TASSERT return value in test_etcp_stcp.c and test_stcp_link.c 2026-06-18 22:11:25 +03:00
Evgeny 73f707ef9b crypto: migrate ECDH(secp256r1) to X25519, drop TinyCrypt
- SC_PUBKEY_SIZE: 64 -> 32 bytes, SC_PUBKEY_ENC_SIZE: 72 -> 40
- sc_set_peer_public_key uses EVP_PKEY_derive with X25519
- sc_generate_keypair via EVP_PKEY_keygen(EVP_PKEY_X25519)
- sc_compute_public_key_from_private via EVP_PKEY_new_raw_private_key
- sc_validate_key: check non-zero (X25519 has no invalid keys)
- sc_obfuscate_pubkey: XOR now 32 bytes
- Remove all TinyCrypt code (~860 lines), always use OpenSSL
- Remove TinyCrypt from build system, drop test_crypto/test_ecc_encrypt
- Update all test keys (128 hex -> 64 hex)
- Fix bbr_integration.c split-line pubkeys
2026-06-17 21:28:07 +03:00
Evgeny 34683f0500 fix: update last_recv_local_time on INIT packet receive to prevent spurious keepalive timeout
last_recv_local_time was not set in the server INIT processing path,
causing the keepalive timer to see a stale timestamp and trigger a
false link-down → route_bgp_remove_conn() right after reconnect.
Routes could be deleted before the first normal packet from the
client updated the timestamp via process_decrypted.
2026-06-17 15:05:20 +03:00
Evgeny 150e9977df stcp: unified transport — etcp_send/bind over TCP via stcp_link
- stcp_link.h/c: manage TCP connections through STCP protocol
  stcp_server_listen, stcp_link_connect/send/close
  rx dispatch goes directly through inst->api_bindings (single table)
- etcp_api.c: etcp_send checks conn->transport_link for TCP path
  etcp_bind stores only in api_bindings (no duplication)
- etcp_connections.c: init_sockets creates stcp_server for TCP
  init_connections creates stcp_link_connect for TCP clients
  tcp_server_on_link creates minimal ETCP_CONN + fires callbacks
- etcp.h: ETCP_CONN.transport_link field (NULL for UDP, set for TCP)
- config_parser: CFG_SERVER.transport field, parsing transport=tcp|udp
- utun_instance.h: struct stcp_server*
- Tests: test_stcp_link (handshake, wrong key)
         test_etcp_stcp (etcp_send/bind over TCP)
- Renamed: transport.h/c → stcp_link.h/c (cleaner, no bindings table)
- 46/46 tests pass
2026-06-16 01:19:58 +03:00
Evgeny f913bdf351 stcp: streaming TCP module + stream cipher + comprehensive tests
- secure_channel: add sc_stream_state + sc_stream_init/sc_stream_xor/sc_stream_cleanup
  AES-128-CTR streaming cipher for incremental encrypt/decrypt
- stcp.h/c: shared stcp_conn lifecycle, set_on_close, rx/tx queue setup
- stcp_server: listen/accept, handshake (obfuscated pubkey + ECDH + stream), DATA phase
- stcp_client: connect, handshake, DATA phase
- Protocol: key_block(72) + enc(data+crc) + padding for handshake
           size(2 LE) + enc(data+crc) for data messages
- Tests: test_stream_cipher (AES-CTR correctness)
         test_stcp (6 cases: sizes 0..65535, 200 msgs, wrong key,
                    close detection, multi-client, interleaved send/recv)
- Fix: 0-length message handling in tx_queue callbacks
2026-06-14 10:00:02 +03:00
Evgeny 0dd942be63 fix: стабилизация тестов test_tcp_io и test_bbr_integration
- test_bbr_integration: bbr_max_cwnd=100KB (16×BDP) — устранён overshoot очереди
- test_tcp_io: адаптирован под EPOLLHUP на socketpair (send до poll, error_cb вместо fin)
- tcp_io: расширенная диагностика error_cb с состоянием сокета
2026-06-11 13:16:48 +03:00
Evgeny 474bb8bd6b bbdr: rename BBR log fields, add mode transition logs, configurable max inflight 2026-06-11 11:06:31 +03:00
Evgeny 3419ff3a85 debug: single source of truth for category names, GUI dynamic labels
- debug_get_category_name(): replace switch/case with table lookup in g_debug_categories[]
- etcpmon_gui: remove hardcoded cat_names[], create labels/radios dynamically from server CSV
2026-06-11 10:16:14 +03:00
Evgeny 6b2b0f1360 etcp: per-link acked bytes/packets counters + BBR debug logging
- Add acked_bytes/acked_packets to struct ETCP_LINK (init, reset, increment per ACK)
- Add DEBUG_CATEGORY_BBR (23) for BBR-specific debug output
- Add BBR input/output DEBUG_DEBUG around bbr_main call in etcp_ack_recv
- Add "bbr" category name to debug_config and etcpmon GUI
2026-06-11 09:55:30 +03:00
Evgeny 20e81e493d proxy recv_cb: silent drop всех известных subcmd при отсутствии conn, не WARN 2026-06-10 13:32:03 +03:00
Evgeny 23732039de SOCKET сообщения жизненного цикла: INFO→DEBUG 2026-06-10 11:18:55 +03:00
Evgeny f883c8da75 Убрал безусловный DEBUG_ERROR в memory_pool_destroy 2026-06-10 11:15:02 +03:00
Evgeny 4211e32f51 tcp_io: единый обработчик ошибок tcp_conn_handle_error
- Вынесена общая логика обработки фатальных ошибок сокета
- Обработчик: close сокета, обнуление коллбэков, аварийный drain, вызов on_error
- Устранён use-after-free: пулы освобождаются до вызова on_error
- flush_write_buf возвращает int (0=ok, 1=error/tc destroyed)
- Все ошибки аллокации write_buf ведут к вызову on_error (ENOMEM)
- Обновлён header: документация в актуальное состояние
2026-06-10 10:38:22 +03:00
Evgeny 7c840c8cfd inflight fix 2026-06-10 01:01:32 +03:00
Evgeny 1a1e305b6b fix: etcp_connection_close — закрывать линки до очистки очередей (SIGSEGV на input_send_q=NULL) 2026-06-09 22:52:44 +03:00
Evgeny 974c6770e0 fix: prevent ack_q unbounded growth on duplicate packets
Check queue_find_data_by_index(ack_q) before creating a new ACK_PACKET
in ETCP_SECTION_PAYLOAD handler. Previously every duplicate packet
created a new entry in ack_q, and with links blocked (inf_block) the
queue grew without bounds.
2026-06-09 22:49:27 +03:00
Evgeny 075c4f4583 memory_pool: name field — идентификация пула в диагностике коррапшна 2026-06-09 22:20:25 +03:00
Evgeny 9e133699bf log_dump: level+category, memory_pool: дамп user_data через log_dump при коррапшне 2026-06-09 22:20:06 +03:00
Evgeny 81cad40e3b memory_pool: усиленная канарейка 4B + хранение alloc_loc для диагностики 2026-06-09 22:19:47 +03:00
Evgeny baca018387 proxy/udp: buf 65536→1600 на стеке, UDP не нужен гигантский буфер 2026-06-09 22:19:30 +03:00
Evgeny 48a4f182aa ll_queue: inline→static inline queue_waiter_call — fix link error with -Og 2026-06-09 22:18:29 +03:00
Evgeny 7c456a1652 proxy: bounds check data_len перед memcpy в handle_data — fix buffer overflow 2026-06-09 22:18:12 +03:00
Evgeny a0ea19acb8 fix: diag_timer_cb UAF — обнуляем rc->diag_timer до раннего возврата 2026-06-09 21:50:04 +03:00
Evgeny 1f34629753 Merge commit 'HEAD@{1}' into bbr
# Conflicts:
#	src/etcp.c
#	src/etcp_connections.c
2026-06-09 19:48:52 +03:00
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 dd1b9f2680 etcp: dedup ack_q — не добавлять дубликаты ACK для одного seq 2026-06-07 21:01:44 +03:00
Evgeny 52f32da3f1 etcp: BBR callback для обновления inflight_lim_bytes + пересчёт optimal_inflight
- BBR: добавлен callback on_cwnd_update(ctx, new_cwnd) в struct bbr, вызывается при изменении cwnd
- etcp_link_update_inflight_lim() — атомарно обновляет inflight_lim_bytes линка и пересчитывает optimal_inflight (сумма по всем линкам)
- BBR теперь сам обновляет лимиты через callback, ручное присваивание в etcp_ack_recv() удалено
- is_server в config_parser.c: печать адресов через sockaddr_storage_to_str (поддержка IPv4/IPv6)
- utun_instance.c: local_sockaddr_equal() — добавлено сравнение AF_INET6
- Убраны отладочные memory_pool_is_freed
2026-06-07 12:17:18 +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
566 changed files with 381038 additions and 3971 deletions

No files matched your search

+6 -1
View File
@@ -1,5 +1,6 @@
# Build directory (out-of-tree build)
/build/
tools/chatgui/build/
# Build outputs
*.o
@@ -59,7 +60,7 @@ Makefile.in
.cache/
# Debug outputs
core
/core
vgcore.*
callgrind.out.*
@@ -96,6 +97,9 @@ utun.conf
tools/bping/bping
tools/proxy/udp_proxy
# Reference projects (not part of utun)
tools/tdesktop-dev/
# Debug/temp artifacts
*.bak
*.debug
@@ -108,3 +112,4 @@ cl
build_win.log
/wintun.dll
src/lwip_orig/
lib/timeout/
+149 -6
View File
@@ -15,7 +15,7 @@ This file contains essential information for AI coding agents working in the uTu
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
## Build Commands
@@ -142,14 +142,14 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
### Key Sizes
| Constant | Value | Description |
|----------|-------|-------------|
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
| `SC_PRIVKEY_SIZE` | 32 | X25519 private key |
| `SC_PUBKEY_SIZE` | 32 | X25519 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 |
| `SC_PUBKEY_ENC_SIZE` | 40 | Total pubkey+salt block sent unencrypted |
### Using Secure Channel (secure_channel.h)
```c
@@ -211,6 +211,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
├── doc/ # Technical Specifications
├── tools/ # Auxiliary tools
│ ├── etcpmon/ # GUI монитор ETCP
│ ├── chatgui/ # GUI чат (Qt 5, отдельная сборка через CMake)
│ ├── tdesktop-dev/ # Референс: Telegram Desktop (только для изучения)
│ ├── proxy/ # UDP прокси для тестов
│ └── bping/ # BPing (bandwidth ping)
├── tinycrypt/ # TinyCrypt crypto library (external)
@@ -246,7 +248,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- `route_node.c/h` - Route node (peer) management
**Crypto (src/)**
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
- `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation
- `crc32.c/h` - CRC32 checksums
**NAT (src/)**
@@ -285,10 +287,15 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- **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
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
## Queue Usage Rules
## Работа с очередями. Важно!
С очередью следуею работать придерживаясь следующих правил:
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
### Запись в очередь
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
@@ -314,6 +321,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
## Bugs Debugging Protocol
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
### Прочие правила:
- sed для редактирования исходников - запрещено
@@ -331,6 +339,141 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## chatgui (GUI Chat Client)
Chatgui — десктопный GUI-чат на Qt 5, отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
### Технологии
- **Язык:** C++17
- **Фреймворк:** Qt 5.15+ (Widgets + Network)
- **Сборка:** CMake 3.16+
- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation)
- **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS)
### Сборка
```bash
# Установка зависимостей
sudo apt install librlottie-dev zlib1g-dev qtbase5-dev
# Сборка
cd tools/chatgui && mkdir -p build && cd build
cmake .. && make -j4
./chatgui
```
### Структура файлов (src/)
| Файл | Назначение |
|------|-----------|
| `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей |
| `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select |
| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate |
| `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций |
| `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` |
| `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией |
| `emojitabbar.h/cpp` | Кастомный QTabBar для эмодзи-панели |
| `inputbar.h/cpp` | Поле ввода (QTextEdit), вставка эмодзи, отправка по Enter |
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
### Другие поддиректории
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, channels, messages, keys, addresses)
- `transport/` — TCP-клиент для связи с uTun (`msg_client.h/cpp`, бинарный протокол `[size:2][type:1][id:8][options:1][data...]`)
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
### Система анимированных эмодзи
#### Формат
TGS = gzip-сжатый Lottie JSON. Холст 512×512, 60fps, 90–180 кадров. Декомпрессия через zlib (`inflateInit2` с `16+MAX_WBITS`).
#### Ключевые компоненты
```
LottieIcon — загрузка TGS, рендер кадров (rlottie C API)
↓
AnimTimer (синглтон) — таймер 33ms, refcount activate/deactivate
├── в сообщениях: → MessageDelegate::paint()
│ drawAnimatedEmojiOverlay() — QTextLayout → позиции эмодзи → drawImage()
│ drawStatusBar() — анимированные реакции вместо drawText()
│
├── в панели: → EmojiButton (наследует QPushButton)
│ per-button QTimer, enterEvent/leaveEvent, собственный paintEvent
│
└── активация: → ChatView::hoveredIndexChanged
MessageList проверяет текст/реакции сообщения на g_animatedEmojiMap
есть анимация → AnimTimer::activate() / нет → deactivate()
```
#### Инициализация
1. `MessageList` конструктор: загружает все `EmojiType::Animation` из `builtinEmojiSet()`, создаёт `LottieIcon`, регистрирует в `AnimTimer`
2. **Должно быть ДО создания `EmojiPanel`** — иначе `EmojiButton` получит `nullptr`
3. `g_animatedEmojiMap` заполняется при старте (Unicode → animPath)
#### Текущий маппинг (5 эмодзи)
| Unicode | Shortcode | TGS-файл |
|---------|-----------|----------|
| ✨ `U+2728` | `:sparkles:` | `sparkles_emoji.tgs` |
| 🏳️ `U+1F3F3` | `:flag:` | `white_flag_emoji.tgs` |
| 👋 `U+1F44B` | `:wave:` | `greeting.tgs` |
| ✋ `U+270B` | `:raised_hand:` | `stop.tgs` |
| 🤖 `U+1F916` | `:robot:` | `robot.tgs` |
#### Рендеринг в сообщениях
- Текст рисуется через `QPainter::drawText(Qt::TextWordWrap)`
- `drawAnimatedEmojiOverlay()` повторяет layout через `QTextLayout` с теми же параметрами
- Для каждого эмодзи из `g_animatedEmojiMap` находит пиксельную позицию через `QTextLine::cursorToX()`
- Рендерит кадр через `LottieIcon::renderFrame(currentFrame(), size)` и рисует `drawImage()`
- Размер эмодзи масштабирован ×1.3 относительно высоты строки
- Аналогично для реакций в `drawStatusBar()`
#### Drag-to-select
- Одиночный клик снимает выделение (если было), не стартует новое
- Движение ≥5px от точки нажатия стартует выделение (`m_selStart`/`m_selEnd`)
- Ctrl+C копирует: для одного сообщения — выделенный фрагмент, для нескольких — `[time] author: text`
### Важные правила chatgui
- `CMAKE_AUTORCC ON` обязателен для компиляции `.qrc`-ресурсов
- Иконки должны быть зарегистрированы в `AnimTimer` ДО создания `EmojiPanel`
- `EmojiButton` НЕ использует `Q_OBJECT` (в анонимном namespace) — таймер через лямбду, каст через `dynamic_cast`
- Координаты `indexAt()`: `viewport()->mapFrom(this, event->pos())`
---
## tdesktop-dev (референсный проект Telegram Desktop)
**Расположение:** `tools/tdesktop-dev/` — полный клон [Telegram Desktop](https://github.com/telegramdesktop/tdesktop).
**Статус:** референс. Не собирается, не модифицируется, в `.gitignore`.
**Назначение:**
- Источник TGS-анимаций (`Telegram/Resources/animations/` — 99+ файлов)
- Образец использования rlottie/lib_lottie API
- Архитектурный референс для GUI-компонентов
**Правила:**
- НЕ модифицировать файлы внутри `tdesktop-dev/`
- НЕ коммитить изменения из `tdesktop-dev/`
- НЕ ссылаться в сборке на код/библиотеки из tdesktop-dev
- Можно копировать TGS-файлы в `tools/chatgui/resources/animations/`
**Полезные поддиректории:**
- `Telegram/Resources/animations/` — TGS-анимации (dice, reactions, emoji, UI)
- `Telegram/lib_lottie/` — C++ обёртка над rlottie (SinglePlayer, MultiPlayer, Icon, FrameGenerator)
- `Telegram/ThirdParty/rlottie/` — submodule rlottie (обычно не выкачан)
**rlottie C API (ключевые функции):**
```c
Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path);
void lottie_animation_get_size(const Lottie_Animation *anim, size_t *w, size_t *h);
size_t lottie_animation_get_totalframe(const Lottie_Animation *anim);
double lottie_animation_get_framerate(const Lottie_Animation *anim);
void lottie_animation_render(Lottie_Animation *anim, size_t frame, uint32_t *buf, size_t w, size_t h, size_t stride);
void lottie_animation_destroy(Lottie_Animation *anim);
```
---
## Key Documentation Files
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
- `/doc/etcp_arch.md` - ETCP архитектура
-28
View File
@@ -1,28 +0,0 @@
# 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
+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 │
└──────────────────────────────────────────────────┘
```
+9 -2
View File
@@ -26,14 +26,21 @@ libuasync_a_SOURCES = \
radix.c \
radix.h \
serialize.c \
serialize.h
serialize.h \
tcp_io.c \
tcp_io.h \
liblmdb/mdb.c \
liblmdb/midl.c \
liblmdb/lmdb.h \
liblmdb/midl.h
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
-DDEBUG_OUTPUT_STDERR \
-g \
-I$(top_srcdir)/src \
-I$(top_srcdir)/lib
-I$(top_srcdir)/lib \
-I$(srcdir)/liblmdb
# Clean build directory
clean-local:
+19 -40
View File
@@ -13,30 +13,29 @@
#include <stdio.h>
#include <time.h>
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) {
if (!debug_should_output(level, category)) return;
char hex_buf[513] = {'\0'};
size_t hex_len = 0;
size_t show_len = (len > 128) ? 128 : len;
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
if (i < show_len - 1 && (i + 1) % 32 == 0)
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
}
}
if (len > 128) {
if (len > 128)
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
switch (level) {
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
}
// Single-line debug output with packet info
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
// Additional debug info for first few bytes
// if (len >= 2) {
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
// prefix, data[0], data[1], data[len-2], data[len-1]);
// }
}
@@ -127,6 +126,8 @@ static const struct {
{"general", DEBUG_CATEGORY_GENERAL},
{"nat", DEBUG_CATEGORY_NAT},
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
{"bbr", DEBUG_CATEGORY_BBR},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -198,31 +199,9 @@ void debug_apply_global_level(const char* level_name) {
/* Get category name for logging */
const char* debug_get_category_name(debug_category_t category_idx) {
switch (category_idx) {
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
case DEBUG_CATEGORY_ETCP: return "ETCP";
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
case DEBUG_CATEGORY_TIMING: return "TIMING";
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
case DEBUG_CATEGORY_TUN: return "TUN";
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
case DEBUG_CATEGORY_BGP: return "BGP";
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
case DEBUG_CATEGORY_DUMP: return "DUMP";
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
case DEBUG_CATEGORY_NAT: return "NAT";
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
case DEBUG_CATEGORY_NONE: return "NONE";
default: return "UNKNOWN";
}
for (int i = 0; g_debug_categories[i].name; i++)
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
return "UNKNOWN";
}
/* Global debug configuration */
+4 -2
View File
@@ -59,7 +59,9 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_COUNT 22 // Total number of categories
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
@@ -122,7 +124,7 @@ 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);
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len);
/* 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);
+23
View File
@@ -0,0 +1,23 @@
mtest
mtest[23456]
testdb
mdb_copy
mdb_stat
mdb_dump
mdb_load
*.lo
*.[ao]
*.so
*.exe
*[~#]
*.bak
*.orig
*.rej
*.gcov
*.gcda
*.gcno
core
core.*
valgrind.*
man/
html/
+317
View File
@@ -0,0 +1,317 @@
LMDB 0.9 Change Log
LMDB 0.9.36 Engineering
ITS#7772 - limit sub-page growth when re-adding existing data
ITS#8335 - more input validation for put(MDB_MULTIPLE)
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
ITS#9388 - fix mdb_stat page counts for dupsort DBs
ITS#10054 - fix write of 2GB values
ITS#10108 - mdb_dump: fix comment
ITS#10454 - fix metapage sync on Mac OSX
ITS#10504 - silence linker waarning on Mac OSX
ITS#10522 - reject 0-length values in DUPSORT DBs
LMDB 0.9.35 Release (2026/01/27)
ITS#10434 - Fix typo affecting Mac OSX
LMDB 0.9.34 Release (2026/01/26)
ITS#9564 - fix race condition freeing spilled pages at end of transaction
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
ITS#10275 - mdb_load: add -Q option to use NOSYNC
ITS#10296 - fix fdatasync on MacOS
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
ITS#10355 - fix mplay build on musl
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
ITS#10419 - add support for NetBSD
ITS#10420 - add support for Haiku
ITS#10421 - mdb_load: check for malicious input
LMDB 0.9.33 Release (2024/05/21)
ITS#9037 mdb_page_search: fix error code when DBI record is missing
ITS#10198 For win32, stop passing ignored parameter
ITS#10212 Fix meta page usage by read only tools
LMDB 0.9.32 Release (2024/01/29)
ITS#9378 - Add ability to replay log and replay log tool
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
ITS#10125 - mdb_load: fix cursor reinit in Append mode
ITS#10137 - Allow users to define MDB_IDL_LOGN
LMDB 0.9.31 Release (2023/07/10)
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
LMDB 0.9.30 Release (2023/02/08)
ITS#9806 - LMDB page_split: key threshold depends on page size
ITS#9916 - avoid gcc optimization bug on sparc64 linux
ITS#9919 - Mark infrequently used functions as cold
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
LMDB 0.9.29 Release (2021/03/16)
ITS#9461 refix ITS#9376
ITS#9500 fix regression from ITS#8662
LMDB 0.9.28 Release (2021/02/04)
ITS#8662 add -a append option to mdb_load
LMDB 0.9.27 Release (2020/10/26)
ITS#9376 fix repeated DUPSORT cursor deletes
LMDB 0.9.26 Release (2020/08/11)
ITS#9278 fix robust mutex cleanup for FreeBSD
LMDB 0.9.25 Release (2020/01/30)
ITS#9068 fix mdb_dump/load backslashes in printable content
ITS#9118 add MAP_NOSYNC for FreeBSD
ITS#9155 free mt_spill_pgs in non-nested txn on end
LMDB 0.9.24 Release (2019/07/24)
ITS#8969 Tweak mdb_page_split
ITS#8975 WIN32 fix writemap set_mapsize crash
ITS#9007 Fix loose pages in WRITEMAP
LMDB 0.9.23 Release (2018/12/19)
ITS#8756 Fix loose pages in dirty list
ITS#8831 Fix mdb_load flag init
ITS#8844 Fix mdb_env_close in forked process
Documentation
ITS#8857 mdb_cursor_del doesn't invalidate cursor
ITS#8908 GET_MULTIPLE etc don't change passed in key
LMDB 0.9.22 Release (2018/03/22)
Fix MDB_DUPSORT alignment bug (ITS#8819)
Fix regression with new db from 0.9.19 (ITS#8760)
Fix liblmdb to build on Solaris (ITS#8612)
Fix delete behavior with DUPSORT DB (ITS#8622)
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
LMDB 0.9.21 Release (2017/06/01)
Fix xcursor after cursor_del (ITS#8622)
LMDB 0.9.20 (Withdrawn)
Fix mdb_load with escaped plaintext (ITS#8558)
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
LMDB 0.9.19 Release (2016/12/28)
Fix mdb_env_cwalk cursor init (ITS#8424)
Fix robust mutexes on Solaris 10/11 (ITS#8339)
Tweak Win32 error message buffer
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
Optimize mdb_drop
Fix xcursors after mdb_cursor_del (ITS#8406)
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
Fix mdb_env_copy with empty DB (ITS#8209)
Fix behaviors with fork (ITS#8505)
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
Fix robust mutexes on kFreeBSD (ITS#8554)
Fix utf8_to_utf16 error checks (ITS#7992)
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
Build
Make shared lib suffix overridable (ITS#8481)
Documentation
Cleanup doxygen nits
Note reserved vs actual mem/disk usage
LMDB 0.9.18 Release (2016/02/05)
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
Fix page_search_root assert on FreeDB (ITS#8336)
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
Fix mdb_copy of large files on Windows
Fix subcursor move after delete (ITS#8355)
Fix mdb_midl_shirnk off-by-one (ITS#8363)
Check for utf8_to_utf16 failures (ITS#7992)
Catch strdup failure in mdb_dbi_open
Build
Additional makefile var tweaks (ITS#8169)
Documentation
Add Getting Started page
Update WRITEMAP description
LMDB 0.9.17 Release (2015/11/30)
Fix ITS#7377 catch calloc failure
Fix ITS#8237 regression from ITS#7589
Fix ITS#8238 page_split for DUPFIXED pages
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
Fix ITS#8258 rebalance/split assert
Fix ITS#8263 cursor_put cursor tracking
Fix ITS#8264 cursor_del cursor tracking
Fix ITS#8310 cursor_del cursor tracking
Fix ITS#8299 mdb_del cursor tracking
Fix ITS#8300 mdb_del cursor tracking
Fix ITS#8304 mdb_del cursor tracking
Fix ITS#7771 fakepage cursor tracking
Fix ITS#7789 ensure mapsize >= pages in use
Fix ITS#7971 mdb_txn_renew0() new reader slots
Fix ITS#7969 use __sync_synchronize on non-x86
Fix ITS#8311 page_split from update_key
Fix ITS#8312 loose pages in nested txn
Fix ITS#8313 mdb_rebalance dummy cursor
Fix ITS#8315 dirty_room in nested txn
Fix ITS#8323 dirty_list in nested txn
Fix ITS#8316 page_merge cursor tracking
Fix ITS#8321 cursor tracking
Fix ITS#8319 mdb_load error messages
Fix ITS#8320 mdb_load plaintext input
Added mdb_txn_id() (ITS#7994)
Added robust mutex support
Miscellaneous cleanup/simplification
Build
Create install dirs if needed (ITS#8256)
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
Added ssize_t typedef for MSVC (ITS#8067)
Use ANSI apis on Windows (ITS#8069)
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
Allow passing AR to make (ITS#8168)
Allow passing mandir to make install (ITS#8169)
LMDB 0.9.16 Release (2015/08/14)
Fix cursor EOF bug (ITS#8190)
Fix handling of subDB records (ITS#8181)
Fix mdb_midl_shrink() usage (ITS#8200)
LMDB 0.9.15 Release (2015/06/19)
Fix txn init (ITS#7961,#7987)
Fix MDB_PREV_DUP (ITS#7955,#7671)
Fix compact of empty env (ITS#7956)
Fix mdb_copy file mode
Fix mdb_env_close() after failed mdb_env_open()
Fix mdb_rebalance collapsing root (ITS#8062)
Fix mdb_load with large values (ITS#8066)
Fix to retry writes on EINTR (ITS#8106)
Fix mdb_cursor_del on empty DB (ITS#8109)
Fix MDB_INTEGERDUP key compare (ITS#8117)
Fix error handling (ITS#7959,#8157,etc.)
Fix race conditions (ITS#7969,7970)
Added workaround for fdatasync bug in ext3fs
Build
Don't use -fPIC for static lib
Update .gitignore (ITS#7952,#7953)
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
Misc. Android/Windows cleanup
Documentation
Fix MDB_APPEND doc
Fix MDB_MAXKEYSIZE doc (ITS#8156)
Fix mdb_cursor_put,mdb_cursor_del EACCES description
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
Clarify MDB_WRITEMAP doc (ITS#8021)
Clarify mdb_env_open doc
Clarify mdb_dbi_open doc
LMDB 0.9.14 Release (2014/09/20)
Fix to support 64K page size (ITS#7713)
Fix to persist decreased as well as increased mapsizes (ITS#7789)
Fix cursor bug when deleting last node of a DUPSORT key
Fix mdb_env_info to return FIXEDMAP address
Fix ambiguous error code from writing to closed DBI (ITS#7825)
Fix mdb_copy copying past end of file (ITS#7886)
Fix cursor bugs from page_merge/rebalance
Fix to dirty fewer pages in deletes (mdb_page_loose())
Fix mdb_dbi_open creating subDBs (ITS#7917)
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
Fix Windows compat issues in mtests (ITS#7879)
Add compacting variant of mdb_copy
Add BigEndian integer key compare code
Add mdb_dump/mdb_load utilities
LMDB 0.9.13 Release (2014/06/18)
Fix mdb_page_alloc unlimited overflow page search
Documentation
Re-fix MDB_CURRENT doc (ITS#7793)
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
LMDB 0.9.12 Release (2014/06/13)
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
Fix mdb_rebalance (ITS#7829)
Fix mdb_page_split (ITS#7815)
Fix md_entries count (ITS#7861,#7828,#7793)
Fix MDB_CURRENT (ITS#7793)
Fix possible crash on Windows DLL detach
Misc code cleanup
Documentation
mdb_cursor_put: cursor moves on error (ITS#7771)
LMDB 0.9.11 Release (2014/01/15)
Add mdb_env_set_assert() (ITS#7775)
Fix: invalidate txn on page allocation errors (ITS#7377)
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
Fix corruption from deletes (ITS#7756)
Fix Windows/MSVC build issues
Raise safe limit of max MDB_MAXKEYSIZE
Misc code cleanup
Documentation
Remove spurious note about non-overlapping flags (ITS#7665)
LMDB 0.9.10 Release (2013/11/12)
Add MDB_NOMEMINIT option
Fix mdb_page_split() again (ITS#7589)
Fix MDB_NORDAHEAD definition (ITS#7734)
Fix mdb_cursor_del() positioning (ITS#7733)
Partial fix for larger page sizes (ITS#7713)
Fix Windows64/MSVC build issues
LMDB 0.9.9 Release (2013/10/24)
Add mdb_env_get_fd()
Add MDB_NORDAHEAD option
Add MDB_NOLOCK option
Avoid wasting space in mdb_page_split() (ITS#7589)
Fix mdb_page_merge() cursor fixup (ITS#7722)
Fix mdb_cursor_del() on last delete (ITS#7718)
Fix adding WRITEMAP on existing env (ITS#7715)
Fix nested txns (ITS#7515)
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
Fix mdb_rebalance() cursor fixup (ITS#7701)
Misc code cleanup
Documentation
Note that by default, readers need write access
LMDB 0.9.8 Release (2013/09/09)
Allow mdb_env_set_mapsize() on an open environment
Fix mdb_dbi_flags() (ITS#7672)
Fix mdb_page_unspill() in nested txns
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
Tweak mdb_page_spill() to be less aggressive
Documentation
Update caveats since mdb_reader_check() added in 0.9.7
LMDB 0.9.7 Release (2013/08/17)
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
Fix mdb_page_split() ref beyond cursor depth
Fix read txn data race (ITS#7635)
Fix mdb_rebalance (ITS#7536, #7538)
Fix mdb_drop() (ITS#7561)
Misc DEBUG macro fixes
Add MDB_NOTLS envflag
Add mdb_env_copyfd()
Add mdb_txn_env() (ITS#7660)
Add mdb_dbi_flags() (ITS#7661)
Add mdb_env_get_maxkeysize()
Add mdb_env_reader_list()/mdb_env_reader_check()
Add mdb_page_spill/unspill, remove hard txn size limit
Use shorter names for semaphores (ITS#7615)
Build
Fix install target (ITS#7656)
Documentation
Misc updates for cursors, DB handles, data lifetime
LMDB 0.9.6 Release (2013/02/25)
Many fixes/enhancements
LMDB 0.9.5 Release (2012/11/30)
Renamed from libmdb to liblmdb
Many fixes/enhancements
+20
View File
@@ -0,0 +1,20 @@
Copyright 2011-2021 Howard Chu, Symas Corp.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted only as authorized by the OpenLDAP
Public License.
A copy of this license is available in the file LICENSE in the
top-level directory of the distribution or, alternatively, at
<http://www.OpenLDAP.org/license.html>.
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
Individual files and/or contributed packages may be copyright by
other parties and/or subject to additional restrictions.
This work also contains materials derived from public sources.
Additional information about OpenLDAP can be obtained at
<http://www.openldap.org/>.
+1614
View File
File diff suppressed because it is too large. Load diff
+47
View File
@@ -0,0 +1,47 @@
The OpenLDAP Public License
Version 2.8, 17 August 2003
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions in source form must retain copyright statements
and notices,
2. Redistributions in binary form must reproduce applicable copyright
statements and notices, this list of conditions, and the following
disclaimer in the documentation and/or other materials provided
with the distribution, and
3. Redistributions must contain a verbatim copy of this document.
The OpenLDAP Foundation may revise this license from time to time.
Each revision is distinguished by a version number. You may use
this Software under terms of this license revision or under the
terms of any subsequent revision of the license.
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED 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 OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
OR OWNER(S) OF THE SOFTWARE 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.
The names of the authors and copyright holders must not be used in
advertising or otherwise to promote the sale, use or other dealing
in this Software without specific, written prior permission. Title
to copyright in this Software shall at all times remain with copyright
holders.
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
California, USA. All Rights Reserved. Permission to copy and
distribute verbatim copies of this document is granted.
+192
View File
@@ -0,0 +1,192 @@
/*
* Copyright 2015-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/** @page starting Getting Started
LMDB is compact, fast, powerful, and robust and implements a simplified
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
documented in its own right.) After reading this page, the main
\ref mdb documentation should make sense. Thanks to Bert Hubert
for creating the
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
initial version</a> of this writeup.
Everything starts with an environment, created by #mdb_env_create().
Once created, this environment must also be opened with #mdb_env_open().
#mdb_env_open() gets passed a name which is interpreted as a directory
path. Note that this directory must exist already, it is not created
for you. Within that directory, a lock file and a storage file will be
generated. If you don't want to use a directory, you can pass the
#MDB_NOSUBDIR option, in which case the path you provided is used
directly as the data file, and another file with a "-lock" suffix
added will be used for the lock file.
Once the environment is open, a transaction can be created within it
using #mdb_txn_begin(). Transactions may be read-write or read-only,
and read-write transactions may be nested. A transaction must only
be used by one thread at a time. Transactions are always required,
even for read-only access. The transaction provides a consistent
view of the data.
Once a transaction has been created, a database can be opened within it
using #mdb_dbi_open(). If only one database will ever be used in the
environment, a NULL can be passed as the database name. For named
databases, the #MDB_CREATE flag must be used to create the database
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
called after #mdb_env_create() and before #mdb_env_open() to set the
maximum number of named databases you want to support.
Note: a single transaction can open multiple databases. Generally
databases should only be opened once, by the first transaction in
the process. After the first transaction completes, the database
handles can freely be used by all subsequent transactions.
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
key/value pairs if that is all you need to do (but see \ref Cursors
below if you want to do more).
A key/value pair is expressed as two #MDB_val structures. This struct
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
an array of \c mv_size bytes.
Because LMDB is very efficient (and usually zero-copy), the data returned
in an #MDB_val structure may be memory-mapped straight from disk. In
other words <b>look but do not touch</b> (or free() for that matter).
Once a transaction is closed, the values can no longer be used, so
make a copy if you need to keep them after that.
@section Cursors Cursors
To do more powerful things, we must use a cursor.
Within the transaction, a cursor can be created with #mdb_cursor_open().
With this cursor we can store/retrieve/delete (multiple) values using
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
#mdb_cursor_get() positions itself depending on the cursor operation
requested, and for some operations, on the supplied key. For example,
to list all key/value pairs in a database, use operation #MDB_FIRST for
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
until the end is hit.
To retrieve all keys starting from a specified key value, use #MDB_SET.
For more cursor operations, see the \ref mdb docs.
When using #mdb_cursor_put(), either the function will position the
cursor for you based on the \b key, or you can use operation
#MDB_CURRENT to use the current position of the cursor. Note that
\b key must then match the current position's key.
@subsection summary Summarizing the Opening
So we have a cursor in a transaction which opened a database in an
environment which is opened from a filesystem after it was
separately created.
Or, we create an environment, open it from a filesystem, create a
transaction within it, open a database within that transaction,
and create a cursor within all of the above.
Got it?
@section thrproc Threads and Processes
LMDB uses POSIX locks on files, and these locks have issues if one
process opens a file multiple times. Because of this, do not
#mdb_env_open() a file multiple times from a single process. Instead,
share the LMDB environment that has opened the file across all threads.
Otherwise, if a single process opens the same environment multiple times,
closing it once will remove all the locks held on it, and the other
instances will be vulnerable to corruption from other processes.
Also note that a transaction is tied to one thread by default using
Thread Local Storage. If you want to pass read-only transactions across
threads, you can use the #MDB_NOTLS option on the environment.
@section txns Transactions, Rollbacks, etc.
To actually get anything done, a transaction must be committed using
#mdb_txn_commit(). Alternatively, all of a transaction's operations
can be discarded using #mdb_txn_abort(). In a read-only transaction,
any cursors will \b not automatically be freed. In a read-write
transaction, all cursors will be freed and must not be used again.
For read-only transactions, obviously there is nothing to commit to
storage. The transaction still must eventually be aborted to close
any database handle(s) opened in it, or committed to keep the
database handles around for reuse in new transactions.
In addition, as long as a transaction is open, a consistent view of
the database is kept alive, which requires storage. A read-only
transaction that no longer requires this consistent view should
be terminated (committed or aborted) when the view is no longer
needed (but see below for an optimization).
There can be multiple simultaneously active read-only transactions
but only one that can write. Once a single read-write transaction
is opened, all further attempts to begin one will block until the
first one is committed or aborted. This has no effect on read-only
transactions, however, and they may continue to be opened at any time.
@section dupkeys Duplicate Keys
#mdb_get() and #mdb_put() respectively have no and only some support
for multiple key/value pairs with identical keys. If there are multiple
values for a key, #mdb_get() will only return the first value.
When multiple values for one key are required, pass the #MDB_DUPSORT
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
#mdb_put() will not replace the value for a key if the key existed
already. Instead it will add the new value to the key. In addition,
#mdb_del() will pay attention to the value field too, allowing for
specific values of a key to be deleted.
Finally, additional cursor operations become available for
traversing through and retrieving duplicate values.
@section optim Some Optimization
If you frequently begin and abort read-only transactions, as an
optimization, it is possible to only reset and renew a transaction.
#mdb_txn_reset() releases any old copies of data kept around for
a read-only transaction. To reuse this reset transaction, call
#mdb_txn_renew() on it. Any cursors in this transaction must also
be renewed using #mdb_cursor_renew().
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
close any databases you opened within the transaction.
To permanently free a transaction, reset or not, use #mdb_txn_abort().
@section cleanup Cleaning Up
For read-only transactions, any cursors created within it must
be closed using #mdb_cursor_close().
It is very rarely necessary to close a database handle, and in
general they should just be left open.
@section onward The Full API
The full \ref mdb documentation lists further details, like how to:
\li size a database (the default limits are intentionally small)
\li drop and clean a database
\li detect and report errors
\li optimize (bulk) loading speed
\li (temporarily) reduce robustness to gain even more speed
\li gather statistics about the database
\li define custom sort orders
*/
+1608
View File
File diff suppressed because it is too large. Load diff
+10504
View File
File diff suppressed because it is too large. Load diff
+55
View File
@@ -0,0 +1,55 @@
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_copy \- LMDB environment copy tool
.SH SYNOPSIS
.B mdb_copy
[\c
.BR \-V ]
[\c
.BR \-c ]
[\c
.BR \-n ]
.B srcpath
[\c
.BR dstpath ]
.SH DESCRIPTION
The
.B mdb_copy
utility copies an LMDB environment. The environment can
be copied regardless of whether it is currently in use.
No lockfile is created, since it gets recreated at need.
If
.I dstpath
is specified it must be the path of an empty directory
for storing the backup. Otherwise, the backup will be
written to stdout.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-c
Compact while copying. Only current data pages will be copied; freed
or unused pages will be omitted from the copy. This option will
slow down the backup process as it is more CPU-intensive.
Currently it fails if the environment has suffered a page leak.
.TP
.BR \-n
Open LDMB environment(s) which do not use subdirectories.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH CAVEATS
This utility can trigger significant file size growth if run
in parallel with write transactions, because pages which they
free during copying cannot be reused until the copy is done.
.SH "SEE ALSO"
.BR mdb_stat (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>
+82
View File
@@ -0,0 +1,82 @@
/* mdb_copy.c - memory-mapped database backup tool */
/*
* Copyright 2012-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#ifdef _WIN32
#include <windows.h>
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
#else
#define MDB_STDOUT 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "lmdb.h"
static void
sighandle(int sig)
{
}
int main(int argc,char * argv[])
{
int rc;
MDB_env *env;
const char *progname = argv[0], *act;
unsigned flags = MDB_RDONLY;
unsigned cpflags = 0;
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
if (argv[1][1] == 'n' && argv[1][2] == '\0')
flags |= MDB_NOSUBDIR;
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
cpflags |= MDB_CP_COMPACT;
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
printf("%s\n", MDB_VERSION_STRING);
exit(0);
} else
argc = 0;
}
if (argc<2 || argc>3) {
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
exit(EXIT_FAILURE);
}
#ifdef SIGPIPE
signal(SIGPIPE, sighandle);
#endif
#ifdef SIGHUP
signal(SIGHUP, sighandle);
#endif
signal(SIGINT, sighandle);
signal(SIGTERM, sighandle);
act = "opening environment";
rc = mdb_env_create(&env);
if (rc == MDB_SUCCESS) {
rc = mdb_env_open(env, argv[1], flags, 0600);
}
if (rc == MDB_SUCCESS) {
act = "copying";
if (argc == 2)
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
else
rc = mdb_env_copy2(env, argv[2], cpflags);
}
if (rc)
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
progname, act, rc, mdb_strerror(rc));
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
+75
View File
@@ -0,0 +1,75 @@
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_dump \- LMDB environment export tool
.SH SYNOPSIS
.B mdb_dump
[\c
.BR \-V ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-l ]
[\c
.BR \-n ]
[\c
.BR \-p ]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.BR \ envpath
.SH DESCRIPTION
The
.B mdb_dump
utility reads a database and writes its contents to the
standard output using a portable flat-text format
understood by the
.BR mdb_load (1)
utility.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-f \ file
Write to the specified file instead of to the standard output.
.TP
.BR \-l
List the databases stored in the environment. Just the
names will be listed, no data will be output.
.TP
.BR \-n
Dump an LMDB database which does not use subdirectories.
.TP
.BR \-p
If characters in either the key or data items are printing characters (as
defined by isprint(3)), output them directly. This option permits users to
use standard text editors and tools to modify the contents of databases.
Note: different systems may have different notions about what characters
are considered printing characters, and databases dumped in this manner may
be less portable to external systems.
.TP
.BR \-a
Dump all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
Dumping databases that use user-defined comparison functions will output
records with the ordering imposed by those comparison functions. If
.B mdb_load
is invoked without including the
.B -a
option when reloading those records, the new databases will likely be
damaged beyond repair, permitting neither record storage nor retrieval.
.SH "SEE ALSO"
.BR mdb_load (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>
+319
View File
@@ -0,0 +1,319 @@
/* mdb_dump.c - memory-mapped database dump tool */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include "lmdb.h"
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
#define PRINT 1
static int mode;
typedef struct flagbit {
int bit;
char *name;
} flagbit;
flagbit dbflags[] = {
{ MDB_REVERSEKEY, "reversekey" },
{ MDB_DUPSORT, "dupsort" },
{ MDB_INTEGERKEY, "integerkey" },
{ MDB_DUPFIXED, "dupfixed" },
{ MDB_INTEGERDUP, "integerdup" },
{ MDB_REVERSEDUP, "reversedup" },
{ 0, NULL }
};
static volatile sig_atomic_t gotsig;
static void dumpsig( int sig )
{
gotsig=1;
}
static const char hexc[] = "0123456789abcdef";
static void hex(unsigned char c)
{
putchar(hexc[c >> 4]);
putchar(hexc[c & 0xf]);
}
static void text(MDB_val *v)
{
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
if (isprint(*c)) {
if (*c == '\\')
putchar('\\');
putchar(*c);
} else {
putchar('\\');
hex(*c);
}
c++;
}
putchar('\n');
}
static void byte(MDB_val *v)
{
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
hex(*c++);
}
putchar('\n');
}
/* Dump in BDB-compatible format */
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
{
MDB_cursor *mc;
MDB_stat ms;
MDB_val key, data;
MDB_envinfo info;
unsigned int flags;
int rc, i;
rc = mdb_dbi_flags(txn, dbi, &flags);
if (rc) return rc;
rc = mdb_stat(txn, dbi, &ms);
if (rc) return rc;
rc = mdb_env_info(mdb_txn_env(txn), &info);
if (rc) return rc;
printf("VERSION=3\n");
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
if (name)
printf("database=%s\n", name);
printf("type=btree\n");
printf("mapsize=%" Z "u\n", info.me_mapsize);
if (info.me_mapaddr)
printf("mapaddr=%p\n", info.me_mapaddr);
printf("maxreaders=%u\n", info.me_maxreaders);
if (flags & MDB_DUPSORT)
printf("duplicates=1\n");
for (i=0; dbflags[i].bit; i++)
if (flags & dbflags[i].bit)
printf("%s=1\n", dbflags[i].name);
printf("db_pagesize=%d\n", ms.ms_psize);
printf("HEADER=END\n");
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) return rc;
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
if (gotsig) {
rc = EINTR;
break;
}
if (mode & PRINT) {
text(&key);
text(&data);
} else {
byte(&key);
byte(&data);
}
}
printf("DATA=END\n");
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
return rc;
}
static void usage(char *prog)
{
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envflags = 0, list = 0;
if (argc < 2) {
usage(prog);
}
/* -a: dump all subDBs
* -s: dump only the named subDB
* -n: use NOSUBDIR flag on env_open
* -p: use printable characters
* -f: write to file instead of stdout
* -V: print version and exit
* (default) dump only the main DB
*/
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'l':
list = 1;
/*FALLTHROUGH*/
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'f':
if (freopen(optarg, "w", stdout) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n",
prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'p':
mode |= PRINT;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
#ifdef SIGPIPE
signal(SIGPIPE, dumpsig);
#endif
#ifdef SIGHUP
signal(SIGHUP, dumpsig);
#endif
signal(SIGINT, dumpsig);
signal(SIGTERM, dumpsig);
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdb_env_set_maxdbs(env, 2);
}
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
int count = 0;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
count++;
str = malloc(key.mv_size+1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdb_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS) {
if (list) {
printf("%s\n", str);
list++;
} else {
rc = dumpit(txn, db2, str);
if (rc)
break;
}
mdb_close(env, db2);
}
free(str);
if (rc) continue;
}
mdb_cursor_close(cursor);
if (!count) {
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
rc = MDB_NOTFOUND;
} else if (rc == MDB_NOTFOUND) {
rc = MDB_SUCCESS;
}
} else {
rc = dumpit(txn, dbi, subname);
}
if (rc && rc != MDB_NOTFOUND)
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
mdb_close(env, dbi);
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
+91
View File
@@ -0,0 +1,91 @@
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_load \- LMDB environment import tool
.SH SYNOPSIS
.B mdb_load
[\c
.BR \-V ]
[\c
.BR \-a ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-n ]
[\c
.BI \-s \ subdb\fR]
[\c
.BR \-N ]
[\c
.BR \-Q ]
[\c
.BR \-T ]
.BR \ envpath
.SH DESCRIPTION
The
.B mdb_load
utility reads from the standard input and loads it into the
LMDB environment
.BR envpath .
The input to
.B mdb_load
must be in the output format specified by the
.BR mdb_dump (1)
utility or as specified by the
.B -T
option below.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-a
Append all records in the order they appear in the input. The input is assumed to already be
in correctly sorted order and no sorting or checking for redundant values will be performed.
This option must be used to reload data that was produced by running
.B mdb_dump
on a database that uses custom compare functions.
.TP
.BR \-f \ file
Read from the specified file instead of from the standard input.
.TP
.BR \-n
Load an LMDB database which does not use subdirectories.
.TP
.BR \-s \ subdb
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
.TP
.BR \-N
Don't overwrite existing records when loading into an already existing database; just skip them.
.TP
.BR \-Q
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
.TP
.BR \-T
Load data from simple text files. The input must be paired lines of text, where the first
line of the pair is the key item, and the second line of the pair is its corresponding
data item.
A simple escape mechanism, where newline and backslash (\\) characters are special, is
applied to the text input. Newline characters are interpreted as record separators.
Backslash characters in the text will be interpreted in one of two ways: If the backslash
character precedes another backslash character, the pair will be interpreted as a literal
backslash. If the backslash character precedes any other character, the two characters
following the backslash will be interpreted as a hexadecimal specification of a single
character; for example, \\0a is a newline character in the ASCII character set.
For this reason, any backslash or newline characters that naturally occur in the text
input must be escaped to avoid misinterpretation by
.BR mdb_load .
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdb_dump (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>
+515
View File
@@ -0,0 +1,515 @@
/* mdb_load.c - memory-mapped database load tool */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "lmdb.h"
#define PRINT 1
#define NOHDR 2
static int mode;
static char *subname = NULL;
static size_t lineno;
static int version;
static int flags;
static char *prog;
static int Eof;
static MDB_envinfo info;
static MDB_val kbuf, dbuf;
static MDB_val k0buf;
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
#define STRLENOF(s) (sizeof(s)-1)
typedef struct flagbit {
int bit;
char *name;
int len;
} flagbit;
#define S(s) s, STRLENOF(s)
flagbit dbflags[] = {
{ MDB_REVERSEKEY, S("reversekey") },
{ MDB_DUPSORT, S("dupsort") },
{ MDB_INTEGERKEY, S("integerkey") },
{ MDB_DUPFIXED, S("dupfixed") },
{ MDB_INTEGERDUP, S("integerdup") },
{ MDB_REVERSEDUP, S("reversedup") },
{ 0, NULL, 0 }
};
static void readhdr(void)
{
char *ptr;
flags = 0;
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
lineno++;
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
if (version > 3) {
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
prog, lineno, version);
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
break;
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
mode |= PRINT;
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
if (subname) free(subname);
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
exit(EXIT_FAILURE);
}
} else {
int i;
for (i=0; dbflags[i].bit; i++) {
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
flags |= dbflags[i].bit;
break;
}
}
if (!dbflags[i].bit) {
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
if (!ptr) {
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
prog, lineno);
exit(EXIT_FAILURE);
} else {
*ptr = '\0';
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
prog, lineno, (char *)dbuf.mv_data);
}
}
}
}
}
static void badend(void)
{
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
prog, lineno);
}
static int unhex(unsigned char *c2)
{
int x, c;
x = *c2++ & 0x4f;
if (x & 0x40)
x -= 55;
c = x << 4;
x = *c2 & 0x4f;
if (x & 0x40)
x -= 55;
c |= x;
return c;
}
static int readline(MDB_val *out, MDB_val *buf)
{
unsigned char *c1, *c2, *end;
size_t len, l2;
int c;
if (!(mode & NOHDR)) {
c = fgetc(stdin);
if (c == EOF) {
Eof = 1;
return EOF;
}
if (c != ' ') {
lineno++;
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
badend:
Eof = 1;
badend();
return EOF;
}
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
return EOF;
goto badend;
}
}
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
Eof = 1;
return EOF;
}
lineno++;
c1 = buf->mv_data;
len = strlen((char *)c1);
if (!len) {
/* This can only happen with an intentionally invalid input
* with a NUL byte after the leading SPACE
*/
goto badend;
}
l2 = len;
/* Is buffer too short? */
while (c1[len-1] != '\n') {
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
if (!buf->mv_data) {
Eof = 1;
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
prog, lineno);
return EOF;
}
c1 = buf->mv_data;
c1 += l2;
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
Eof = 1;
badend();
return EOF;
}
buf->mv_size *= 2;
len = strlen((char *)c1);
l2 += len;
}
c1 = c2 = buf->mv_data;
len = l2;
c1[--len] = '\0';
end = c1 + len;
if (mode & PRINT) {
while (c2 < end) {
if (*c2 == '\\') {
if (c2[1] == '\\') {
*c1++ = *c2;
} else {
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(++c2);
}
c2 += 2;
} else {
/* copies are redundant when no escapes were used */
*c1++ = *c2++;
}
}
} else {
/* odd length not allowed */
if (len & 1) {
Eof = 1;
badend();
return EOF;
}
while (c2 < end) {
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(c2);
c2 += 2;
}
}
c2 = out->mv_data = buf->mv_data;
out->mv_size = c1 - c2;
return 0;
}
static void usage(void)
{
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
exit(EXIT_FAILURE);
}
static int greater(const MDB_val *a, const MDB_val *b)
{
return 1;
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_cursor *mc;
MDB_dbi dbi;
char *envname;
int envflags = MDB_NOSYNC, putflags = 0;
int dohdr = 0, append = 0;
MDB_val prevk;
prog = argv[0];
if (argc < 2) {
usage();
}
/* -a: append records in input order
* -f: load file instead of stdin
* -n: use NOSUBDIR flag on env_open
* -s: load into named subDB
* -N: use NOOVERWRITE on puts
* -Q: quick mode using NOSYNC
* -T: read plaintext
* -V: print version and exit
*/
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'a':
append = 1;
break;
case 'f':
if (freopen(optarg, "r", stdin) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n",
prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 's':
subname = strdup(optarg);
break;
case 'N':
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
break;
case 'Q':
envflags |= MDB_NOSYNC;
break;
case 'T':
mode |= NOHDR | PRINT;
break;
default:
usage();
}
}
if (optind != argc - 1)
usage();
dbuf.mv_size = 4096;
dbuf.mv_data = malloc(dbuf.mv_size);
if (!(mode & NOHDR))
readhdr();
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
mdb_env_set_maxdbs(env, 2);
if (info.me_maxreaders)
mdb_env_set_maxreaders(env, info.me_maxreaders);
if (info.me_mapsize)
mdb_env_set_mapsize(env, info.me_mapsize);
if (info.me_mapaddr)
envflags |= MDB_FIXEDMAP;
rc = mdb_env_open(env, envname, envflags, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
kbuf.mv_data = malloc(kbuf.mv_size * 2);
k0buf.mv_size = kbuf.mv_size;
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
prevk.mv_data = k0buf.mv_data;
while(!Eof) {
MDB_val key, data;
int batch = 0;
flags = 0;
int appflag;
if (!dohdr) {
dohdr = 1;
} else if (!(mode & NOHDR))
readhdr();
rc = mdb_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
if (rc) {
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
prevk.mv_size = 0;
if (append) {
mdb_set_compare(txn, dbi, greater);
if (flags & MDB_DUPSORT)
mdb_set_dupsort(txn, dbi, greater);
}
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while(1) {
rc = readline(&key, &kbuf);
if (rc) /* rc == EOF */
break;
rc = readline(&data, &dbuf);
if (rc) {
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
goto txn_abort;
}
if (append) {
appflag = MDB_APPEND;
if (flags & MDB_DUPSORT) {
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
appflag = MDB_CURRENT|MDB_APPENDDUP;
else {
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
prevk.mv_size = key.mv_size;
}
}
} else {
appflag = 0;
}
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
if (rc == MDB_KEYEXIST && putflags)
continue;
if (rc) {
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
goto txn_abort;
}
batch++;
if (batch == 100) {
rc = mdb_txn_commit(txn);
if (rc) {
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
prog, lineno, mdb_strerror(rc));
goto env_close;
}
rc = mdb_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
if (append) {
MDB_val k, d;
mdb_cursor_get(mc, &k, &d, MDB_LAST);
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
prevk.mv_size = k.mv_size;
}
batch = 0;
}
}
rc = mdb_txn_commit(txn);
txn = NULL;
if (rc) {
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
prog, lineno, mdb_strerror(rc));
goto env_close;
}
if (envflags & MDB_NOSYNC) {
rc = mdb_env_sync(env, 1);
if (rc) {
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
}
mdb_dbi_close(env, dbi);
}
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
+64
View File
@@ -0,0 +1,64 @@
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_stat \- LMDB environment status tool
.SH SYNOPSIS
.B mdb_stat
[\c
.BR \-V ]
[\c
.BR \-e ]
[\c
.BR \-f [ f [ f ]]]
[\c
.BR \-n ]
[\c
.BR \-r [ r ]]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.BR \ envpath
.SH DESCRIPTION
The
.B mdb_stat
utility displays the status of an LMDB environment.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-e
Display information about the database environment.
.TP
.BR \-f
Display information about the environment freelist.
If \fB\-ff\fP is given, summarize each freelist entry.
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
.TP
.BR \-n
Display the status of an LMDB database which does not use subdirectories.
.TP
.BR \-r
Display information about the environment reader table.
Shows the process ID, thread ID, and transaction ID for each active
reader slot. The process ID and transaction ID are in decimal, the
thread ID is in hexadecimal. The transaction ID is displayed as "-"
if the reader does not currently have a read transaction open.
If \fB\-rr\fP is given, check for stale entries in the reader
table and clear them. The reader table will be printed again
after the check is performed.
.TP
.BR \-a
Display the status of all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Display the status of a specific subdatabase.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdb_copy (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>
+263
View File
@@ -0,0 +1,263 @@
/* mdb_stat.c - memory-mapped database status tool */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lmdb.h"
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
static void prstat(MDB_stat *ms)
{
#if 0
printf(" Page size: %u\n", ms->ms_psize);
#endif
printf(" Tree depth: %u\n", ms->ms_depth);
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
printf(" Entries: %"Z"u\n", ms->ms_entries);
}
static void usage(char *prog)
{
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
MDB_stat mst;
MDB_envinfo mei;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
if (argc < 2) {
usage(prog);
}
/* -a: print stat of main DB and all subDBs
* -s: print stat of only the named subDB
* -e: print env info
* -f: print freelist info
* -r: print reader info
* -n: use NOSUBDIR flag on env_open
* -V: print version and exit
* (default) print stat of only the main DB
*/
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'e':
envinfo++;
break;
case 'f':
freinfo++;
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'r':
rdrinfo++;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdb_env_set_maxdbs(env, 4);
}
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
if (envinfo) {
(void)mdb_env_stat(env, &mst);
(void)mdb_env_info(env, &mei);
printf("Environment Info\n");
printf(" Map address: %p\n", mei.me_mapaddr);
printf(" Map size: %"Z"u\n", mei.me_mapsize);
printf(" Page size: %u\n", mst.ms_psize);
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
printf(" Max readers: %u\n", mei.me_maxreaders);
printf(" Number of readers used: %u\n", mei.me_numreaders);
}
if (rdrinfo) {
printf("Reader Table Status\n");
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
if (rdrinfo > 1) {
int dead;
mdb_reader_check(env, &dead);
printf(" %d stale readers cleared.\n", dead);
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
}
if (!(subname || alldbs || freinfo))
goto env_close;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
if (freinfo) {
MDB_cursor *cursor;
MDB_val key, data;
size_t pages = 0, *iptr;
printf("Freelist Status\n");
dbi = 0;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
rc = mdb_stat(txn, dbi, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
prstat(&mst);
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
iptr = data.mv_data;
pages += *iptr;
if (freinfo > 1) {
char *bad = "";
size_t pg, prev;
ssize_t i, j, span = 0;
j = *iptr++;
for (i = j, prev = 1; --i >= 0; ) {
pg = iptr[i];
if (pg <= prev)
bad = " [bad sequence]";
prev = pg;
pg += span;
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
}
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
*(size_t *)key.mv_data, j, span, bad);
if (freinfo > 2) {
for (--j; j >= 0; ) {
pg = iptr[j];
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
pg, span);
}
}
}
}
mdb_cursor_close(cursor);
printf(" Free pages: %"Z"u\n", pages);
}
rc = mdb_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
rc = mdb_stat(txn, dbi, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
printf("Status of %s\n", subname ? subname : "Main DB");
prstat(&mst);
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
str = malloc(key.mv_size+1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdb_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS)
printf("Status of %s\n", str);
free(str);
if (rc) continue;
rc = mdb_stat(txn, db2, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
prstat(&mst);
mdb_close(env, db2);
}
mdb_cursor_close(cursor);
}
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
mdb_close(env, dbi);
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
+359
View File
@@ -0,0 +1,359 @@
/** @file midl.c
* @brief ldap bdb back-end ID List functions */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2000-2021 The OpenLDAP Foundation.
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include "midl.h"
/** @defgroup internal LMDB Internals
* @{
*/
/** @defgroup idls ID List Management
* @{
*/
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
{
/*
* binary search of id in ids
* if found, returns position of id
* if not found, returns first position greater than id
*/
unsigned base = 0;
unsigned cursor = 1;
int val = 0;
unsigned n = ids[0];
while( 0 < n ) {
unsigned pivot = n >> 1;
cursor = base + pivot + 1;
val = CMP( ids[cursor], id );
if( val < 0 ) {
n = pivot;
} else if ( val > 0 ) {
base = cursor;
n -= pivot + 1;
} else {
return cursor;
}
}
if( val > 0 ) {
++cursor;
}
return cursor;
}
#if 0 /* superseded by append/sort */
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
{
unsigned x, i;
x = mdb_midl_search( ids, id );
assert( x > 0 );
if( x < 1 ) {
/* internal error */
return -2;
}
if ( x <= ids[0] && ids[x] == id ) {
/* duplicate */
assert(0);
return -1;
}
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
/* no room */
--ids[0];
return -2;
} else {
/* insert id */
for (i=ids[0]; i>x; i--)
ids[i] = ids[i-1];
ids[x] = id;
}
return 0;
}
#endif
MDB_IDL mdb_midl_alloc(int num)
{
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
if (ids) {
*ids++ = num;
*ids = 0;
}
return ids;
}
void mdb_midl_free(MDB_IDL ids)
{
if (ids)
free(ids-1);
}
void mdb_midl_shrink( MDB_IDL *idp )
{
MDB_IDL ids = *idp;
if (*(--ids) > MDB_IDL_UM_MAX &&
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
{
*ids++ = MDB_IDL_UM_MAX;
*idp = ids;
}
}
static int mdb_midl_grow( MDB_IDL *idp, int num )
{
MDB_IDL idn = *idp-1;
/* grow it */
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
if (!idn)
return ENOMEM;
*idn++ += num;
*idp = idn;
return 0;
}
int mdb_midl_need( MDB_IDL *idp, unsigned num )
{
MDB_IDL ids = *idp;
num += ids[0];
if (num > ids[-1]) {
num = (num + num/4 + (256 + 2)) & -256;
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
return ENOMEM;
*ids++ = num - 2;
*idp = ids;
}
return 0;
}
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
{
MDB_IDL ids = *idp;
/* Too big? */
if (ids[0] >= ids[-1]) {
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
return ENOMEM;
ids = *idp;
}
ids[0]++;
ids[ids[0]] = id;
return 0;
}
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
{
MDB_IDL ids = *idp;
/* Too big? */
if (ids[0] + app[0] >= ids[-1]) {
if (mdb_midl_grow(idp, app[0]))
return ENOMEM;
ids = *idp;
}
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
ids[0] += app[0];
return 0;
}
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
{
MDB_ID *ids = *idp, len = ids[0];
/* Too big? */
if (len + n > ids[-1]) {
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
return ENOMEM;
ids = *idp;
}
ids[0] = len + n;
ids += len;
while (n)
ids[n--] = id++;
return 0;
}
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
{
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
old_id = idl[j];
while (i) {
merge_id = merge[i--];
for (; old_id < merge_id; old_id = idl[--j])
idl[k--] = old_id;
idl[k--] = merge_id;
}
idl[0] = total;
}
/* Quicksort + Insertion sort for small arrays */
#define SMALL 8
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
void
mdb_midl_sort( MDB_IDL ids )
{
/* Max possible depth of int-indexed tree * 2 items/level */
int istack[sizeof(int)*CHAR_BIT * 2];
int i,j,k,l,ir,jstack;
MDB_ID a, itmp;
ir = (int)ids[0];
l = 1;
jstack = 0;
for(;;) {
if (ir - l < SMALL) { /* Insertion sort */
for (j=l+1;j<=ir;j++) {
a = ids[j];
for (i=j-1;i>=1;i--) {
if (ids[i] >= a) break;
ids[i+1] = ids[i];
}
ids[i+1] = a;
}
if (jstack == 0) break;
ir = istack[jstack--];
l = istack[jstack--];
} else {
k = (l + ir) >> 1; /* Choose median of left, center, right */
MIDL_SWAP(ids[k], ids[l+1]);
if (ids[l] < ids[ir]) {
MIDL_SWAP(ids[l], ids[ir]);
}
if (ids[l+1] < ids[ir]) {
MIDL_SWAP(ids[l+1], ids[ir]);
}
if (ids[l] < ids[l+1]) {
MIDL_SWAP(ids[l], ids[l+1]);
}
i = l+1;
j = ir;
a = ids[l+1];
for(;;) {
do i++; while(ids[i] > a);
do j--; while(ids[j] < a);
if (j < i) break;
MIDL_SWAP(ids[i],ids[j]);
}
ids[l+1] = ids[j];
ids[j] = a;
jstack += 2;
if (ir-i+1 >= j-l) {
istack[jstack] = ir;
istack[jstack-1] = i;
ir = j-1;
} else {
istack[jstack] = j-1;
istack[jstack-1] = l;
l = i;
}
}
}
}
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
{
/*
* binary search of id in ids
* if found, returns position of id
* if not found, returns first position greater than id
*/
unsigned base = 0;
unsigned cursor = 1;
int val = 0;
unsigned n = (unsigned)ids[0].mid;
while( 0 < n ) {
unsigned pivot = n >> 1;
cursor = base + pivot + 1;
val = CMP( id, ids[cursor].mid );
if( val < 0 ) {
n = pivot;
} else if ( val > 0 ) {
base = cursor;
n -= pivot + 1;
} else {
return cursor;
}
}
if( val > 0 ) {
++cursor;
}
return cursor;
}
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
{
unsigned x, i;
x = mdb_mid2l_search( ids, id->mid );
if( x < 1 ) {
/* internal error */
return -2;
}
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
/* duplicate */
return -1;
}
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
/* too big */
return -2;
} else {
/* insert id */
ids[0].mid++;
for (i=(unsigned)ids[0].mid; i>x; i--)
ids[i] = ids[i-1];
ids[x] = *id;
}
return 0;
}
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
{
/* Too big? */
if (ids[0].mid >= MDB_IDL_UM_MAX) {
return -2;
}
ids[0].mid++;
ids[ids[0].mid] = *id;
return 0;
}
/** @} */
/** @} */
+188
View File
@@ -0,0 +1,188 @@
/** @file midl.h
* @brief LMDB ID List header file.
*
* This file was originally part of back-bdb but has been
* modified for use in libmdb. Most of the macros defined
* in this file are unused, just left over from the original.
*
* This file is only used internally in libmdb and its definitions
* are not exposed publicly.
*/
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2000-2021 The OpenLDAP Foundation.
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#ifndef _MDB_MIDL_H_
#define _MDB_MIDL_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @defgroup internal LMDB Internals
* @{
*/
/** @defgroup idls ID List Management
* @{
*/
/** A generic unsigned ID number. These were entryIDs in back-bdb.
* Preferably it should have the same size as a pointer.
*/
typedef size_t MDB_ID;
/** An IDL is an ID List, a sorted array of IDs. The first
* element of the array is a counter for how many actual
* IDs are in the list. In the original back-bdb code, IDLs are
* sorted in ascending order. For libmdb IDLs are sorted in
* descending order.
*/
typedef MDB_ID *MDB_IDL;
/* IDL sizes - likely should be even bigger
* limiting factors: sizeof(ID), thread stack size
*/
#ifndef MDB_IDL_LOGN
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
#endif
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
/** Current max length of an #mdb_midl_alloc()ed IDL */
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
/** Append ID to IDL. The IDL must be big enough. */
#define mdb_midl_xappend(idl, id) do { \
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
xidl[xlen] = (id); \
} while (0)
/** Search for an ID in an IDL.
* @param[in] ids The IDL to search.
* @param[in] id The ID to search for.
* @return The index of the first ID greater than or equal to \b id.
*/
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
/** Allocate an IDL.
* Allocates memory for an IDL of the given size.
* @return IDL on success, NULL on failure.
*/
MDB_IDL mdb_midl_alloc(int num);
/** Free an IDL.
* @param[in] ids The IDL to free.
*/
void mdb_midl_free(MDB_IDL ids);
/** Shrink an IDL.
* Return the IDL to the default size if it has grown larger.
* @param[in,out] idp Address of the IDL to shrink.
*/
void mdb_midl_shrink(MDB_IDL *idp);
/** Make room for num additional elements in an IDL.
* @param[in,out] idp Address of the IDL.
* @param[in] num Number of elements to make room for.
* @return 0 on success, ENOMEM on failure.
*/
int mdb_midl_need(MDB_IDL *idp, unsigned num);
/** Append an ID onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] id The ID to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
/** Append an IDL onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] app The IDL to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
/** Append an ID range onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] id The lowest ID to append.
* @param[in] n Number of IDs to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
* @param[in] idl The IDL to merge into.
* @param[in] merge The IDL to merge.
*/
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
/** Sort an IDL.
* @param[in,out] ids The IDL to sort.
*/
void mdb_midl_sort( MDB_IDL ids );
/** An ID2 is an ID/pointer pair.
*/
typedef struct MDB_ID2 {
MDB_ID mid; /**< The ID */
void *mptr; /**< The pointer */
} MDB_ID2;
/** An ID2L is an ID2 List, a sorted array of ID2s.
* The first element's \b mid member is a count of how many actual
* elements are in the array. The \b mptr member of the first element is unused.
* The array is sorted in ascending order by \b mid.
*/
typedef MDB_ID2 *MDB_ID2L;
/** Search for an ID in an ID2L.
* @param[in] ids The ID2L to search.
* @param[in] id The ID to search for.
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
*/
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
/** Insert an ID2 into a ID2L.
* @param[in,out] ids The ID2L to insert into.
* @param[in] id The ID2 to insert.
* @return 0 on success, -1 if the ID was already present in the ID2L.
*/
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
/** Append an ID2 into a ID2L.
* @param[in,out] ids The ID2L to append into.
* @param[in] id The ID2 to append.
* @return 0 on success, -2 if the ID2L is too big.
*/
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* _MDB_MIDL_H_ */
+580
View File
@@ -0,0 +1,580 @@
/* mplay.c - memory-mapped database log replay */
/*
* Copyright 2011-2023 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
#define MDB_SCNy(t) "z" #t
#define SCMP(s) s, (sizeof(s)-1)
char inbuf[8192];
char *dbuf, *kbuf;
size_t dbufsize;
int maxkey;
#define SOFF(s) (sizeof(s)+1)
#define MAXENVS 16
#define MAXTXNS 16
#define MAXCRSS 16
#define MAXPIDS 16
typedef struct crspair {
void *tcrs; /* scanned text pointer */
MDB_cursor *rcrs;
} crspair;
typedef struct txnpair {
void *ttxn; /* scanned text pointer */
MDB_txn *rtxn;
crspair cursors[MAXCRSS];
int ncursors;
} txnpair;
typedef struct envpair {
void *tenv;
MDB_env *renv;
txnpair txns[MAXTXNS];
int ntxns;
} envpair;
envpair envs[MAXENVS];
int nenvs;
envpair *lastenv;
txnpair *lasttxn;
crspair *lastcrs;
typedef struct pidpair {
int tpid;
pid_t rpid;
int fdout, fdin;
} pidpair;
pidpair *lastpid;
pidpair pids[MAXPIDS];
int npids;
unsigned long lcount;
static int unhex(unsigned char *c2)
{
int x, c;
x = *c2++ & 0x4f;
if (x & 0x40)
x -= 55;
c = x << 4;
x = *c2 & 0x4f;
if (x & 0x40)
x -= 55;
c |= x;
return c;
}
int inhex(char *in, char *out)
{
char *c2 = out;
while (isxdigit(*in)) {
*c2++ = unhex((unsigned char *)in);
in += 2;
}
return c2 - out;
}
static void addenv(void *tenv, MDB_env *renv)
{
assert(nenvs < MAXENVS);
envs[nenvs].tenv = tenv;
envs[nenvs].renv = renv;
envs[nenvs].ntxns = 0;
lastenv = envs+nenvs;
nenvs++;
}
static envpair *findenv(void *tenv)
{
int i;
if (!lastenv || lastenv->tenv != tenv) {
for (i=0; i<nenvs; i++)
if (envs[i].tenv == tenv)
break;
assert(i < nenvs);
lastenv = &envs[i];
}
return lastenv;
}
static void delenv(envpair *ep)
{
int i = ep - envs;
for (; i<nenvs-1; i++)
envs[i] = envs[i+1];
nenvs--;
lastenv = NULL;
}
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
{
envpair *ep;
txnpair *tp;
ep = findenv(tenv);
assert(ep->ntxns < MAXTXNS);
tp = ep->txns+ep->ntxns;
tp->ttxn = ttxn;
tp->rtxn = rtxn;
tp->ncursors = 0;
ep->ntxns++;
lasttxn = tp;
}
static txnpair *findtxn(void *ttxn)
{
int i, j;
if (lasttxn && lasttxn->ttxn == ttxn)
return lasttxn;
if (lastenv) {
for (i=0; i<lastenv->ntxns; i++) {
if (lastenv->txns[i].ttxn == ttxn) {
lasttxn = lastenv->txns+i;
return lasttxn;
}
}
}
for (i=0; i<nenvs; i++) {
if (envs+i == lastenv) continue;
for (j=0; j<envs[i].ntxns; j++) {
if (envs[i].txns[j].ttxn == ttxn) {
lastenv = envs+i;
lasttxn = envs[i].txns+j;
return lasttxn;
}
}
}
assert(0); /* should have found it */
}
static void deltxn(txnpair *tp)
{
int i = tp - lastenv->txns;
for (; i<lastenv->ntxns-1; i++)
lastenv->txns[i] = lastenv->txns[i+1];
lastenv->ntxns--;
lasttxn = NULL;
}
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
{
int j = tp->ncursors;
assert(tp->ncursors < MAXCRSS);
tp->cursors[j].tcrs = tcrs;
tp->cursors[j].rcrs = rcrs;
tp->ncursors++;
lastcrs = tp->cursors+j;
}
static crspair *findcrs(void *tcrs)
{
int i, j, k;
envpair *ep;
txnpair *tp;
crspair *cp;
if (lastcrs && lastcrs->tcrs == tcrs)
return lastcrs;
if (lasttxn) {
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
if (cp->tcrs == tcrs) {
lastcrs = cp;
return lastcrs;
}
}
}
if (lastenv) {
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
if (tp == lasttxn)
continue;
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
if (cp->tcrs == tcrs) {
lastcrs = cp;
lasttxn = tp;
return lastcrs;
}
}
}
}
for (i=0, ep=envs; i<nenvs; i++, ep++) {
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
if (tp == lasttxn)
continue;
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
if (cp->tcrs == tcrs) {
lastcrs = cp;
lasttxn = tp;
lastenv = ep;
return lastcrs;
}
}
}
}
assert(0); /* should have found it already */
}
static void delcrs(void *tcrs)
{
int i;
crspair *cp = findcrs(tcrs);
mdb_cursor_close(cp->rcrs);
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
lasttxn->cursors[i] = lasttxn->cursors[i+1];
lasttxn->ncursors--;
lastcrs = NULL;
}
void child()
{
int rc;
MDB_val key, data;
char *ptr;
while (fgets(inbuf, sizeof(inbuf), stdin)) {
ptr = inbuf;
if (!strncmp(ptr, SCMP("exit")))
break;
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
void *tenv;
MDB_env *renv;
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
E(mdb_env_create(&renv));
addenv(tenv, renv);
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
void *tenv;
envpair *ep;
unsigned int maxdbs;
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
ep = findenv(tenv);
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
void *tenv;
envpair *ep;
size_t mapsize;
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
ep = findenv(tenv);
E(mdb_env_set_mapsize(ep->renv, mapsize));
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
void *tenv;
envpair *ep;
char *path;
int len;
unsigned int flags, mode;
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
path = ptr+SOFF("mdb_env_open")+len;
ptr = strchr(path, ',');
*ptr++ = '\0';
sscanf(ptr, "%u, %o", &flags, &mode);
ep = findenv(tenv);
E(mdb_env_open(ep->renv, path, flags, mode));
if (!maxkey) {
maxkey = mdb_env_get_maxkeysize(ep->renv);
kbuf = malloc(maxkey+2);
dbuf = malloc(maxkey+2);
dbufsize = maxkey;
}
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
void *tenv;
envpair *ep;
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
ep = findenv(tenv);
mdb_env_close(ep->renv);
delenv(ep);
if (!nenvs) /* if no other envs left, this process is done */
break;
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
unsigned int flags;
void *tenv, *ttxn;
envpair *ep;
MDB_txn *rtxn;
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
ep = findenv(tenv);
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
addtxn(tenv, ttxn, rtxn);
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
void *ttxn;
txnpair *tp;
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
tp = findtxn(ttxn);
E(mdb_txn_commit(tp->rtxn));
deltxn(tp);
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
void *ttxn;
txnpair *tp;
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
tp = findtxn(ttxn);
mdb_txn_abort(tp->rtxn);
deltxn(tp);
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
void *ttxn;
txnpair *tp;
char *dbname;
unsigned int flags;
unsigned int tdbi;
MDB_dbi dbi;
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
dbname += 2;
ptr = strchr(dbname, ',');
*ptr++ = '\0';
if (!strcmp(dbname, "(null)"))
dbname = NULL;
sscanf(ptr, "%u = %u", &flags, &tdbi);
tp = findtxn(ttxn);
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
void *tenv;
envpair *ep;
unsigned int tdbi;
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
ep = findenv(tenv);
mdb_dbi_close(ep->renv, tdbi);
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
void *ttxn, *tcrs;
txnpair *tp;
MDB_cursor *rcrs;
unsigned int tdbi;
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
tp = findtxn(ttxn);
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
addcrs(tp, tcrs, rcrs);
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
void *tcrs;
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
delcrs(tcrs);
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
void *tcrs;
crspair *cp;
unsigned int flags;
int len;
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
cp = findcrs(tcrs);
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
if (key.mv_size) {
ptr = strchr(ptr, '[');
inhex(ptr+1, kbuf);
key.mv_data = kbuf;
ptr += key.mv_size * 2 + 2;
}
ptr = strchr(ptr+1, ',');
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
if (data.mv_size > dbufsize) {
dbuf = realloc(dbuf, data.mv_size+2);
assert(dbuf != NULL);
dbufsize = data.mv_size;
}
ptr += len+1;
if (*ptr == '[') {
inhex(ptr+1, dbuf);
data.mv_data = dbuf;
ptr += data.mv_size * 2 + 2;
} else {
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
}
sscanf(ptr+1, "%u", &flags);
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
void *tcrs;
crspair *cp;
unsigned int flags;
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
cp = findcrs(tcrs);
E(mdb_cursor_del(cp->rcrs, flags));
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
void *ttxn;
txnpair *tp;
unsigned int tdbi, flags;
int len;
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
tp = findtxn(ttxn);
ptr = strchr(ptr+SOFF("mdb_put"), '[');
inhex(ptr+1, kbuf);
key.mv_data = kbuf;
ptr += key.mv_size * 2 + 2;
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
if (data.mv_size > dbufsize) {
dbuf = realloc(dbuf, data.mv_size+2);
assert(dbuf != NULL);
dbufsize = data.mv_size;
}
ptr += len+1;
if (*ptr == '[') {
inhex(ptr+1, dbuf);
ptr += data.mv_size * 2 + 2;
} else {
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
}
data.mv_data = dbuf;
sscanf(ptr+1, "%u", &flags);
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
void *ttxn;
txnpair *tp;
unsigned int tdbi;
int len;
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
tp = findtxn(ttxn);
ptr = strchr(ptr+SOFF("mdb_del"), '[');
inhex(ptr+1, kbuf);
key.mv_data = kbuf;
ptr += key.mv_size * 2 + 2;
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
if (data.mv_size > dbufsize) {
dbuf = realloc(dbuf, data.mv_size+2);
assert(dbuf != NULL);
dbufsize = data.mv_size;
}
ptr += len+1;
if (*ptr == '[') {
inhex(ptr+1, dbuf);
} else {
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
}
data.mv_data = dbuf;
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
}
write(1, "\n", 1);
}
exit(0);
}
static pidpair *addpid(int tpid)
{
int fdout[2], fdin[2];
pid_t pid;
assert(npids < MAXPIDS);
pids[npids].tpid = tpid;
pipe(fdout);
pipe(fdin);
if ((pid = fork()) == 0) {
/* child */
fflush(stdin);
fflush(stdout);
dup2(fdout[0], 0);
dup2(fdin[1], 1);
child();
return 0; /* NOTREACHED */
} else {
pids[npids].rpid = pid;
pids[npids].fdout = fdout[1];
pids[npids].fdin = fdin[0];
lastpid = pids+npids;
npids++;
return lastpid;
}
}
static pidpair *findpid(int tpid)
{
int i;
if (!lastpid || lastpid->tpid != tpid) {
for (i=0; i<npids; i++)
if (pids[i].tpid == tpid)
break;
if (i == npids)
return NULL;
lastpid = &pids[i];
}
return lastpid;
}
volatile pid_t killpid;
static void delpid(int tpid)
{
pidpair *pp = findpid(tpid);
if (pp) {
pid_t kpid = pp->rpid;
killpid = kpid;
write(pp->fdout, "exit\n", sizeof("exit"));
while (killpid == kpid)
usleep(10000);
}
}
static void reaper(int sig)
{
int status, i;
pid_t pid = waitpid(-1, &status, 0);
if (pid > 0) {
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
for (i=0; i<npids; i++)
if (pids[i].rpid == pid)
break;
assert(i < npids);
close(pids[i].fdout);
close(pids[i].fdin);
for (;i<npids-1; i++)
pids[i] = pids[i+1];
npids--;
killpid = 0;
}
}
int main(int argc,char * argv[])
{
signal(SIGCHLD, reaper);
while (fgets(inbuf, sizeof(inbuf), stdin)) {
pidpair *pp;
int tpid, len;
char c, *ptr;
lcount++;
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
delpid(tpid);
continue;
}
if (inbuf[0] != '>')
continue;
ptr = inbuf+1;
sscanf(ptr, "%d:%n", &tpid, &len);
pp = findpid(tpid);
if (!pp)
pp = addpid(tpid); /* new process */
ptr = inbuf+len+1;
len = strlen(ptr);
write(pp->fdout, ptr, len); /* send command and wait for ack */
read(pp->fdin, &c, 1);
}
while (npids)
delpid(pids[0].tpid);
}
+177
View File
@@ -0,0 +1,177 @@
/* mtest.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor, *cur2;
MDB_cursor_op op;
int count;
int *values;
char sval[32] = "";
srand(time(NULL));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_maxreaders(env, 1));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, NULL, 0, &dbi));
key.mv_size = sizeof(int);
key.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
j++;
data.mv_size = sizeof(sval);
data.mv_data = sval;
}
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
key.mv_data = sval;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%03x ", values[i]);
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor last\n");
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor last/prev\n");
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
printf("Deleting with cursor\n");
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cur2));
for (i=0; i<50; i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
E(mdb_del(txn, dbi, &key, NULL));
}
printf("Restarting cursor in txn\n");
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cur2);
E(mdb_txn_commit(txn));
printf("Restarting cursor outside txn\n");
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+124
View File
@@ -0,0 +1,124 @@
/* mtest2.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Just like mtest.c, but using a subDB instead of the main DB */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32] = "";
srand(time(NULL));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_maxreaders(env, 1));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
key.mv_size = sizeof(int);
key.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
key.mv_data = sval;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%03x ", values[i]);
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+133
View File
@@ -0,0 +1,133 @@
/* mtest3.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32];
char kval[sizeof(int)];
srand(time(NULL));
memset(sval, 0, sizeof(sval));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
if (!(i & 0x0f))
sprintf(kval, "%03x", values[i]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
/* mtest4.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs with fixed-size keys */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[8];
char kval[sizeof(int)];
memset(sval, 0, sizeof(sval));
count = 510;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = i*5;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
strcpy(kval, "001");
for (i=0;i<count;i++) {
sprintf(sval, "%07x", values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
/* there should be one full page of dups now.
*/
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
/* test all 3 branches of split code:
* 1: new key in lower half
* 2: new key at split point
* 3: new key in upper half
*/
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
sprintf(sval, "%07x", values[3]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
mdb_txn_abort(txn);
sprintf(sval, "%07x", values[255]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
mdb_txn_abort(txn);
sprintf(sval, "%07x", values[500]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
E(mdb_txn_commit(txn));
/* Try MDB_NEXT_MULTIPLE */
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%3)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%07x", values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+135
View File
@@ -0,0 +1,135 @@
/* mtest5.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs using cursor_put */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32];
char kval[sizeof(int)];
srand(time(NULL));
memset(sval, 0, sizeof(sval));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
if (!(i & 0x0f))
sprintf(kval, "%03x", values[i]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
mdb_cursor_close(cursor);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+141
View File
@@ -0,0 +1,141 @@
/* mtest6.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for DB splits and merges */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
char dkbuf[1024];
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data, sdata;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
long kval;
char *sval;
srand(time(NULL));
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
E(mdb_stat(txn, dbi, &mst));
sval = calloc(1, mst.ms_psize / 4);
key.mv_size = sizeof(long);
key.mv_data = &kval;
sdata.mv_size = mst.ms_psize / 4 - 30;
sdata.mv_data = sval;
printf("Adding 12 values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5;
sprintf(sval, "%08x", kval);
data = sdata;
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
printf("Adding 12 more values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5+4;
sprintf(sval, "%08x", kval);
data = sdata;
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
printf("Adding 12 more values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5+1;
sprintf(sval, "%08x", kval);
data = sdata;
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
do {
printf("key: %p %s, data: %p %.*s\n",
key.mv_data, mdb_dkey(&key, dkbuf),
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_commit(txn);
#if 0
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_dbi_close(env, dbi);
#endif
mdb_env_close(env);
return 0;
}
+73
View File
@@ -0,0 +1,73 @@
/* sample-bdb.txt - BerkeleyDB toy/sample
*
* Do a line-by-line comparison of this and sample-mdb.txt
*/
/*
* Copyright 2012-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <string.h>
#include <db.h>
int main(int argc,char * argv[])
{
int rc;
DB_ENV *env;
DB *dbi;
DBT key, data;
DB_TXN *txn;
DBC *cursor;
char sval[32], kval[32];
/* Note: Most error checking omitted for simplicity */
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
rc = db_env_create(&env, 0);
rc = env->open(env, "./testdb", FLAGS, 0664);
rc = db_create(&dbi, env, 0);
rc = env->txn_begin(env, NULL, &txn, 0);
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.size = sizeof(int);
key.data = sval;
data.size = sizeof(sval);
data.data = sval;
sprintf(sval, "%03x %d foo bar", 32, 3141592);
rc = dbi->put(dbi, txn, &key, &data, 0);
rc = txn->commit(txn, 0);
if (rc) {
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
goto leave;
}
rc = env->txn_begin(env, NULL, &txn, 0);
rc = dbi->cursor(dbi, txn, &cursor, 0);
key.flags = DB_DBT_USERMEM;
key.data = kval;
key.ulen = sizeof(kval);
data.flags = DB_DBT_USERMEM;
data.data = sval;
data.ulen = sizeof(sval);
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.data, (int) key.size, (char *) key.data,
data.data, (int) data.size, (char *) data.data);
}
rc = cursor->c_close(cursor);
rc = txn->abort(txn);
leave:
rc = dbi->close(dbi, 0);
rc = env->close(env, 0);
return rc;
}
+62
View File
@@ -0,0 +1,62 @@
/* sample-mdb.txt - MDB toy/sample
*
* Do a line-by-line comparison of this and sample-bdb.txt
*/
/*
* Copyright 2012-2021 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include "lmdb.h"
int main(int argc,char * argv[])
{
int rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_cursor *cursor;
char sval[32];
/* Note: Most error checking omitted for simplicity */
rc = mdb_env_create(&env);
rc = mdb_env_open(env, "./testdb", 0, 0664);
rc = mdb_txn_begin(env, NULL, 0, &txn);
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
key.mv_size = sizeof(int);
key.mv_data = sval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
sprintf(sval, "%03x %d foo bar", 32, 3141592);
rc = mdb_put(txn, dbi, &key, &data, 0);
rc = mdb_txn_commit(txn);
if (rc) {
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
goto leave;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
rc = mdb_cursor_open(txn, dbi, &cursor);
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
leave:
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return 0;
}
+22
View File
@@ -0,0 +1,22 @@
<tagfile>
<compound kind="page">
<name>mdb_copy_1</name>
<title>mdb_copy - environment copy tool</title>
<filename>mdb_copy.1</filename>
</compound>
<compound kind="page">
<name>mdb_dump_1</name>
<title>mdb_dump - environment export tool</title>
<filename>mdb_dump.1</filename>
</compound>
<compound kind="page">
<name>mdb_load_1</name>
<title>mdb_load - environment import tool</title>
<filename>mdb_load.1</filename>
</compound>
<compound kind="page">
<name>mdb_stat_1</name>
<title>mdb_stat - environment status tool</title>
<filename>mdb_stat.1</filename>
</compound>
</tagfile>
+194 -43
View File
@@ -46,8 +46,23 @@ static inline void queue_check_thread(struct ll_queue* q) {
// Предварительные объявления внутренних функций
static void queue_resume_timeout_cb(void* arg);
static void check_waiters(struct ll_queue* q);
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
static void empty_trampoline(void* arg);
static void on_get_trampoline(void* arg);
static void empty_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
q->empty_call_soon_id = NULL;
queue_callback_fn cb = q->empty_callback;
void* cb_arg = q->empty_callback_arg;
q->empty_callback = NULL;
q->empty_callback_arg = NULL;
if (q->count == 0 && cb) cb(q, cb_arg);
}
static void on_get_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
if (q->on_get) q->on_get(q, q->on_get_arg);
}
// ==================== Управление очередью ====================
@@ -60,6 +75,9 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
q->name = name;
q->ua = ua;
q->size_limit = -1; // Без ограничения по умолчанию
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
q->waiter_defer = 0;
q->hash_size = hash_size;
q->index_offset = index_offset;
q->index_size = index_size;
@@ -112,6 +130,17 @@ void queue_free(struct ll_queue* q) {
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
// Это упрощает архитектуру и предотвращает double-u_free
// Освободить список waiters (сбросить handle->internal)
struct queue_waiter* w = q->waiter_head;
while (w) {
struct queue_waiter* next = w->next;
if (w->handle) w->handle->internal = NULL;
u_free(w);
w = next;
}
q->waiter_head = NULL;
q->waiter_tail = NULL;
// Освободить хеш-таблицу
if (q->hash_table) {
u_free(q->hash_table);
@@ -122,6 +151,14 @@ void queue_free(struct ll_queue* q) {
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
q->resume_timeout_id = NULL;
}
void* old_eid = q->empty_call_soon_id;
q->empty_call_soon_id = NULL;
if (old_eid) uasync_call_soon_cancel(q->ua, old_eid);
q->empty_callback = NULL;
q->empty_callback_arg = NULL;
q->on_get = NULL;
q->on_get_arg = NULL;
u_free(q);
}
@@ -266,24 +303,44 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
return hash;
}
// Проверить и запустить ожидающие коллбэки
static void waiter_defer_cb(void* arg) {
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
h->defer_cb(h->defer_q, h->defer_arg);
}
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
struct queue_waiter_handle* h) {
if (q->waiter_defer) {
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
uasync_call_soon(q->ua, h, waiter_defer_cb);
} else {
cb(q, arg);
}
}
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
static void check_waiters(struct ll_queue* q) {
if (!q) return;
if (!q || !q->waiter_head) return;
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
const int cond = q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
if (!cond) return;
struct queue_waiter* waiter = &q->waiter;
struct queue_waiter* waiter = q->waiter_head;
q->waiter_head = waiter->next;
if (!q->waiter_head) q->waiter_tail = NULL;
if (waiter->callback) {
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
waiter->callback(q, waiter->callback_arg);
memset(waiter, 0, sizeof(*waiter));
}
struct queue_waiter_handle* h = waiter->handle;
if (h) h->internal = NULL;
queue_threshold_callback_fn cb = waiter->callback;
void* arg = waiter->callback_arg;
u_free(waiter);
if (cb) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
queue_waiter_call(q, cb, arg, h);
}
}
@@ -293,6 +350,12 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
abort();
}
if (q->hash_size > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
queue_dgram_free(entry);
@@ -334,6 +397,12 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
queue_dgram_free(entry);
@@ -377,6 +446,12 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
if (q->hash_size > 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
queue_dgram_free(entry);
@@ -410,6 +485,12 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
}
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
queue_dgram_free(entry);
@@ -476,6 +557,12 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
// Проверить ожидающие коллбэки
check_waiters(q);
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
if (q->on_get)
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
#ifdef QUEUE_DEBUG
queue_check_consistency(q);// !!!! for debug
#endif
@@ -489,14 +576,27 @@ int queue_entry_count(struct ll_queue* q) {
// Функция проверки консистентности count и total_bytes
// Возвращает 0 если ok, -1 если есть несоответствия
int queue_check_consistency(struct ll_queue* q) {
if (!q) return -1; // Недопустимая очередь
if (!q) return -1;
// Проверка: если count > 0, то head не должен быть NULL
if (q->count > 0 && !q->head) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': count=%d but head is NULL!",
q->name ? q->name : "unknown", q->count);
return -1;
}
// Floyd cycle detection — за O(N) без риска зависания
{
struct ll_entry *slow = q->head, *fast = q->head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': CYCLE detected! entry=%p count=%d",
q->name ? q->name : "unknown", (void*)slow, q->count);
abort();
}
}
}
int actual_count = 0;
size_t actual_bytes = 0;
@@ -507,7 +607,6 @@ int queue_check_consistency(struct ll_queue* q) {
actual_bytes += current->int_len;
if (current->next) {
if (current->next->prev != current) {
// Несоответствие в связях prev/next
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': prev/next error at entry %p != %p entries: %d!=%d bytes: %zu!=%zu",
q->name ? q->name : "unknown", (void*)current, (void*)current->next->prev, actual_count, q->count, actual_bytes, q->total_bytes);
return -1;
@@ -532,34 +631,86 @@ int queue_check_consistency(struct ll_queue* q) {
return 0;
}
// ==================== Асинхронное ожидание ====================
// ==================== Пороговое ожидание (backpressure) ====================
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
struct queue_waiter* waiter = &q->waiter;
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк немедленно
callback(q, arg);
return NULL;
}
// Установить waiter для отложенного вызова
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
return waiter;
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
if (!q) return;
q->threshold_max_packets = max_packets;
q->threshold_max_bytes = max_bytes;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter || waiter != &q->waiter) return;
void queue_set_waiter_defer(struct ll_queue* q, int enable) {
if (!q) return;
q->waiter_defer = enable;
}
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
void* old = q->empty_call_soon_id;
q->empty_call_soon_id = NULL;
if (old) uasync_call_soon_cancel(q->ua, old);
q->empty_callback = cbk_fn;
q->empty_callback_arg = arg;
if (cbk_fn && q->count == 0)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
}
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->on_get = cbk_fn;
q->on_get_arg = arg;
}
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !h) return -1;
if (h->internal) queue_waiter_cancel(q, h);
if (q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
queue_waiter_call(q, callback, arg, h);
return 1;
}
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
if (!waiter) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
return -1;
}
waiter->next = NULL;
waiter->handle = h;
waiter->callback = callback;
waiter->callback_arg = arg;
h->internal = waiter;
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
q->waiter_tail = waiter;
return 0;
}
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
memset(waiter, 0, sizeof(*waiter));
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
struct queue_waiter* curr = q->waiter_head;
while (curr && curr != waiter) {
prev = curr;
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
h->internal = NULL;
return;
}
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
if (waiter == q->waiter_tail) q->waiter_tail = prev;
h->internal = NULL;
u_free(waiter);
}
// ==================== Поиск и удаление по ID ====================
+85 -20
View File
@@ -12,7 +12,7 @@
#include <pthread.h>
#endif
#define QUEUE_DEBUG 1
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
/**
@@ -26,7 +26,7 @@
* - Обязательный вызов queue_resume_callback после обработки элемента
* - Поддержка пулов памяти для entry и отдельно для dgram
* - Хеш-таблица для быстрого поиска по ID (опционально)
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
* - Связный список waiters для backpressure c общим порогом (round-robin)
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
*
* @note Важные правила использования автозабора (queue_set_callback):
@@ -37,13 +37,15 @@
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
*
* @note Память:
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
* (handle->internal каждого ожидающего сбрасывается в NULL)
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
*/
// Forward declaration
// Forward declarations
struct ll_queue;
struct queue_waiter_handle;
/**
* @struct ll_entry
@@ -84,7 +86,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
/**
* @typedef queue_threshold_callback_fn
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
* @brief Одноразовый коллбэк при достижении порога очереди.
* @param q указатель на очередь
* @param arg пользовательский аргумент
*/
@@ -92,13 +94,29 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
/**
* @struct queue_waiter
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
*/
struct queue_waiter {
int max_packets; ///< Максимально допустимое количество элементов
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
void* callback_arg; ///< Аргумент коллбэка
struct queue_waiter* next; ///< Следующий в списке ожидающих
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
queue_threshold_callback_fn callback;
void* callback_arg;
};
/**
* @struct queue_waiter_handle
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
*
* Вызывающая сторона:
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
*/
struct queue_waiter_handle {
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
struct ll_queue* defer_q;
queue_threshold_callback_fn defer_cb;
void* defer_arg;
};
/**
@@ -120,7 +138,18 @@ struct ll_queue {
void* resume_timeout_id; // ID таймера uasync для отложенного resume
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
struct queue_waiter waiter; // Встроенный waiter (только один)
struct queue_waiter* waiter_head; // Голова списка ожидающих
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
void* empty_callback_arg;
void* empty_call_soon_id; // handle от uasync_call_soon
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
void* on_get_arg;
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
@@ -190,26 +219,62 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание ==================== */
/* ==================== Пороговое ожидание (backpressure) ==================== */
/**
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
* @brief Устанавливает общий порог освобождения очереди.
* @param q очередь
* @param max_packets максимальное количество элементов
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
* @param max_bytes максимальный объём данных (0 = не проверять)
*
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
*/
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
void queue_set_waiter_defer(struct ll_queue* q, int enable);
/**
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
*/
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*/
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Регистрирует ожидание освобождения очереди до общего порога.
* @param q очередь
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
* @param callback функция, вызываемая при достижении порога
* @param arg аргумент коллбэка
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
*
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
*/
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
/**
* @brief Отменяет ранее установленное ожидание порога.
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
* @param q очередь
* @param waiter указатель, возвращённый queue_wait_threshold()
* @param h указатель на handle
*
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
*/
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */
+24 -39
View File
@@ -14,8 +14,9 @@
#define PADDING_FILL 0xAA
#define METADATA_SIZE 128
#define POINTER_SIZE sizeof(void*)
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE)
#define NEXT_OFFSET LOCATION_MAX
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
#define PREV_OFFSET LOCATION_MAX
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
#include <execinfo.h>
@@ -136,8 +137,12 @@ void* u_malloc_impl(uint32_t size, const char* location) {
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
// Add to list (thread-safe)
LOCK();
void** prev_ptr = (void**)(base + PREV_OFFSET);
void** next_ptr = (void**)(base + NEXT_OFFSET);
*prev_ptr = NULL;
*next_ptr = allocated_head;
if (allocated_head)
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base;
allocated_head = base;
allocated_count++;
allocations_count++;
@@ -175,29 +180,14 @@ void u_free_impl(void* ptr, const char* location) {
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
char* alloc_loc = (char*)base;
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
// Remove from linked list (thread-safe)
// Remove from doubly-linked list (O(1))
LOCK();
void* prev = NULL;
void* curr = allocated_head;
bool found = false;
while (curr) {
if (curr == base) {
if (prev) {
*(void**)((uint8_t*)prev + NEXT_OFFSET) = *(void**)((uint8_t*)curr + NEXT_OFFSET);
} else {
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
allocated_count--;
frees_count++;
found = true;
break;
}
prev = curr;
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
}
void* prev = *(void**)(base + PREV_OFFSET);
void* next = *(void**)(base + NEXT_OFFSET);
size_t active_count = allocated_count;
UNLOCK();
if (!found) {
if (prev == NULL && allocated_head != base) {
UNLOCK();
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
@@ -215,24 +205,19 @@ void u_free_impl(void* ptr, const char* location) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
hex_dump(ptr, dump_size);
}
#if BACKTRACE_ENABLED
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Backtrace ---");
# if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
void* bt_buf[32];
int bt_size = backtrace(bt_buf, 32);
char** bt_symbols = backtrace_symbols(bt_buf, bt_size);
if (bt_symbols) {
for (int i = 0; i < bt_size; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %s", i, bt_symbols[i]);
free(bt_symbols);
}
# elif defined(_WIN32)
void* bt_buf[32];
USHORT frames = CaptureStackBackTrace(0, 32, bt_buf, NULL);
for (USHORT i = 0; i < frames; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %p", i, bt_buf[i]);
# endif
#endif
exit(EXIT_FAILURE);
}
if (prev)
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next;
else
allocated_head = next;
if (next)
*(void**)((uint8_t*)next + PREV_OFFSET) = prev;
allocated_count--;
frees_count++;
UNLOCK();
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
memset(base, 0, total);
free(base);
+66 -13
View File
@@ -5,6 +5,49 @@
#include "memory_pool.h"
#include "mem.h"
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
#define POOL_CANARY_OFF (pool->object_size)
#define POOL_LOC_OFF (pool->object_size + 4)
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
#define POOL_CANARY_VAL 0xDEADBEAF
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
*canary = POOL_CANARY_VAL;
*loc = location;
*counter = 1;
}
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
if (*canary != POOL_CANARY_VAL) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
if (pool->object_size)
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_MEMORY, " user_data", obj, pool->object_size);
volatile int _halt = 1;
while (_halt) {}
}
if (*counter == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
*loc ? *loc : "(null)", 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) {
@@ -12,7 +55,7 @@ size_t memory_pool_get_total_free_blocks(void) {
}
// Инициализировать пул памяти
struct memory_pool* memory_pool_init(size_t object_size) {
struct memory_pool* memory_pool_init(size_t object_size, const char* name) {
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
if (!pool) {
return NULL;
@@ -22,6 +65,8 @@ 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;
pool->name = name;
return pool;
}
@@ -33,18 +78,21 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
if (pool->free_head) {
void* obj = pool->free_head;
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
pool->free_head = *(void**)obj;
pool->free_count--;
g_total_free--;
pool->reuse_count++;
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
memset(obj, 0, pool->object_size);
pool_init_tags(pool, obj, location);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
return obj;
}
pool->allocations++;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
return u_calloc_impl(1, pool->object_size, location); // Используем calloc для инициализации нулями
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
if (obj) pool_init_tags(pool, obj, location);
return obj;
}
// Освободить объект в пул или в free
@@ -53,9 +101,10 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
return;
}
// Если пул не заполнен, сохранить объект для повторного использования
pool_check_and_clear_tags(pool, obj, location);
if (pool->free_count < MEMORY_POOL_MAX_FREE) {
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
*(void**)obj = pool->free_head;
pool->free_head = obj;
pool->free_count++;
g_total_free++;
@@ -63,7 +112,6 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
return;
}
// Иначе освободить через free
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
u_free_impl(obj, location);
}
@@ -87,21 +135,26 @@ 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;
}
+10 -6
View File
@@ -12,16 +12,18 @@
#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 байт)
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
};
// сам пул:
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
struct memory_pool* memory_pool_init(size_t object_size);
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
void memory_pool_destroy(struct memory_pool* pool);
// элементы пула:
@@ -33,4 +35,6 @@ size_t memory_pool_get_total_free_blocks(void);
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
#endif // MEMORY_POOL_H
+441
View File
@@ -0,0 +1,441 @@
// 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 int flush_write_buf(struct tcp_conn* tc);
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
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, int rcvbuf_size,
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->rcvbuf_size = rcvbuf_size;
tc->on_fin = on_fin;
tc->on_error = on_error;
tc->arg = arg;
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
{
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
tc->data_pool = memory_pool_init(ds, "data_pool");
}
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;
if (rcvbuf_size > 0) {
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
else
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
}
{
struct sockaddr_storage addr;
socklen_t alen = sizeof(addr);
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
return tc;
}
void tcp_conn_destroy(struct tcp_conn* tc) {
if (!tc) return;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
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 tcp_conn_handle_error(struct tcp_conn* tc, int err)
{
tc->error = 1;
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);
if (tc->sock != SOCKET_INVALID) {
while (1) {
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) break;
uint8_t* buf = memory_pool_alloc(tc->data_pool);
if (!buf) { queue_entry_free(e); break; }
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); }
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
}
}
if (tc->socket_id) {
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
}
if (tc->sock != SOCKET_INVALID) {
socket_close_wrapper(tc->sock);
tc->sock = SOCKET_INVALID;
}
queue_set_callback(tc->write_queue, NULL, NULL);
if (tc->on_error) {
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
void* arg = tc->arg;
tc->on_error = NULL;
on_err(tc, err, arg);
}
}
// ====================================================================
// Чтение из сокета
// ====================================================================
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_DEBUG(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;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
tcp_conn_handle_error(tc, errno);
}
}
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 int 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 0;
}
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
tcp_conn_handle_error(tc, errno);
return 1;
}
return 0;
}
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) {
if (flush_write_buf(tc)) return;
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_DEBUG(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_DEBUG(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);
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, ENOMEM);
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);
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, ENOMEM);
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 {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, errno);
}
}
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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
tcp_conn_handle_error(tc, err ? err : -1);
return;
}
}
if (flush_write_buf(tc)) return;
if (!tc->write_buf) {
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
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;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
tcp_conn_handle_error(tc, -1);
}
// ====================================================================
// 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_DEBUG(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_DEBUG(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;
}
+110
View File
@@ -0,0 +1,110 @@
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
//
// Один tcp_conn = одно TCP-соединение. Два пула:
// entry_pool — struct ll_entry (без inline data)
// data_pool — буферы (max(entry_data_size, write_chunk_size))
//
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
// high_water/low_water управляют backpressure (EPOLLIN on/off).
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
//
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
// Всё отправлено → on_flushed.
//
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
// FIN → shutdown(SHUT_WR) → on_fin_sent
// CLOSE → close сокета → on_closed
//
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
#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;
int rcvbuf_size;
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)
// Частичная отправка (из data_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); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
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,
int rcvbuf_size,
void (*on_fin)(struct tcp_conn* tc, void* arg),
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg);
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
void tcp_conn_destroy(struct tcp_conn* tc);
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
// Автозабор (deferred) сам шлёт когда сокет готов.
// Порог: 32 записи, при заполнении 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
+141 -43
View File
@@ -42,17 +42,19 @@ struct timeout_node {
};
// Socket node with array-based storage
struct socket_node {
int fd; // File descriptor (for pipe, file)
socket_t sock; // Socket (for cross-platform sockets)
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
socket_callback_t read_cbk; // For FD type
socket_callback_t write_cbk; // For FD type
socket_t_callback_t read_cbk_sock; // For SOCK type
socket_t_callback_t write_cbk_sock; // For SOCK type
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if u_freed (for reuse)
struct socket_node {
int fd; // File descriptor (for pipe, file)
socket_t sock; // Socket (for cross-platform sockets)
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
socket_callback_t read_cbk; // For FD type
socket_callback_t write_cbk; // For FD type
socket_t_callback_t read_cbk_sock; // For SOCK type
socket_t_callback_t write_cbk_sock; // For SOCK type
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if u_freed (for reuse)
int enable_read; // 1 if read monitoring is enabled
int enable_write; // 1 if write monitoring is enabled
};
// Array-based socket management for O(1) operations
@@ -187,10 +189,12 @@ static int socket_array_add_internal(struct socket_array* sa, int fd, socket_t s
sa->sockets[index].write_cbk = write_cbk_fd;
sa->sockets[index].read_cbk_sock = read_cbk_sock;
sa->sockets[index].write_cbk_sock = write_cbk_sock;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->sockets[index].enable_read = (read_cbk_fd != NULL || read_cbk_sock != NULL) ? 1 : 0;
sa->sockets[index].enable_write = (write_cbk_fd != NULL || write_cbk_sock != NULL) ? 1 : 0;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
sa->active_indices[sa->count] = index; // Add to active list
@@ -240,6 +244,8 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
sa->sockets[index].write_cbk_sock = NULL;
sa->sockets[index].except_cbk = NULL;
sa->sockets[index].user_data = NULL;
sa->sockets[index].enable_read = 0;
sa->sockets[index].enable_write = 0;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
@@ -444,6 +450,7 @@ static void process_timeouts(struct UASYNC* ua) {
}
if (node && node->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→immediate %s", node->name[0] ? node->name : "");
node->callback(node->arg);
}
@@ -469,7 +476,7 @@ static void process_timeouts(struct UASYNC* ua) {
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
// Execute callback only if not cancelled
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→%s expired", node->name[0] ? node->name : "");
node->callback(node->arg);
}
@@ -755,10 +762,84 @@ err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
ua->poll_fds_dirty = 1;
return ERR_OK;
}
return ERR_FAIL;
}
// Helper function to rebuild cached pollfd array
return ERR_FAIL;
}
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (!node->active || node->fd < 0) return ERR_FAIL;
int val = enable ? 1 : 0;
if (node->enable_read == val) return ERR_OK;
node->enable_read = val;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = 0;
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->sock;
} else {
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->fd;
}
if (node->except_cbk) ev.events |= EPOLLPRI;
#ifdef _WIN32
int efd = (int)(intptr_t)ev.data.fd;
#else
int efd = ev.data.fd;
#endif
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
}
#endif
ua->poll_fds_dirty = 1;
return ERR_OK;
}
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (!node->active || node->fd < 0) return ERR_FAIL;
int val = enable ? 1 : 0;
if (node->enable_write == val) return ERR_OK;
node->enable_write = val;
#if HAS_EPOLL
if (ua->use_epoll && ua->epoll_fd >= 0) {
struct epoll_event ev;
ev.events = 0;
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->sock;
} else {
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
ev.data.fd = node->fd;
}
if (node->except_cbk) ev.events |= EPOLLPRI;
#ifdef _WIN32
int efd = (int)(intptr_t)ev.data.fd;
#else
int efd = ev.data.fd;
#endif
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
}
#endif
ua->poll_fds_dirty = 1;
return ERR_OK;
}
// Helper function to rebuild cached pollfd array
static void rebuild_poll_fds(struct UASYNC* ua) {
if (!ua || !ua->sockets) return;
@@ -808,14 +889,14 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
ua->poll_fds[idx].events = 0;
ua->poll_fds[idx].revents = 0;
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
if (cur->read_cbk_sock) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk_sock) ua->poll_fds[idx].events |= POLLOUT;
} else {
if (cur->read_cbk) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
}
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
if (cur->read_cbk_sock && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk_sock && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
} else {
if (cur->read_cbk && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
}
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
if (cur->except_cbk) ua->poll_fds[idx].events |= POLLPRI;
idx++;
@@ -840,6 +921,8 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
// Socket event — save node fields locally: callbacks may realloc
// the socket array, invalidating `node`.
struct socket_node* node = socket_array_get(ua->sockets, events[i].data.fd);
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d ev=0x%x%s", events[i].data.fd, events[i].events,
!node || !node->active ? " (inactive)" : "");
if (!node || !node->active) continue;
int local_fd = node->fd;
socket_t local_sock = node->sock;
@@ -892,7 +975,9 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
if (!ua->sockets || !ua->timeout_heap) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "poll");
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, %zu timers, timeout=%d.%dms)",
ua->sockets->count, ua->timeout_heap->size, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
// Handle negative or zero timeout
if (timeout_tb < 0) timeout_tb = -1; // Infinite wait
@@ -1012,12 +1097,12 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
s = (SOCKET)node->fd;
}
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock) FD_SET(s, &read_fds);
if (node->write_cbk_sock) FD_SET(s, &write_fds);
} else {
if (node->read_cbk) FD_SET(s, &read_fds);
if (node->write_cbk) FD_SET(s, &write_fds);
if (node->type == SOCKET_NODE_TYPE_SOCK) {
if (node->read_cbk_sock && node->enable_read) FD_SET(s, &read_fds);
if (node->write_cbk_sock && node->enable_write) FD_SET(s, &write_fds);
} else {
if (node->read_cbk && node->enable_read) FD_SET(s, &read_fds);
if (node->write_cbk && node->enable_write) FD_SET(s, &write_fds);
}
if (node->except_cbk) FD_SET(s, &except_fds);
@@ -1055,7 +1140,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
int has_write = FD_ISSET(s, &write_fds);
int has_except = FD_ISSET(s, &except_fds);
if (!has_read && !has_write && !has_except) continue;
if (!has_read && !has_write && !has_except) continue;
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "select→fd=%d r=%d w=%d e=%d", (int)s, has_read, has_write, has_except);
if (has_except) {
if (node->except_cbk) {
@@ -1113,7 +1199,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
/* Process socket events first to give sockets higher priority */
if (ret > 0) {
for (int i = 0; i < ua->poll_fds_count; i++) {
if (ua->poll_fds[i].revents == 0) continue;
if (ua->poll_fds[i].revents == 0) continue;
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d rev=0x%x", ua->poll_fds[i].fd, ua->poll_fds[i].revents);
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
@@ -1260,7 +1347,7 @@ struct UASYNC* uasync_create(void) {
}
// Initialize timeout pool
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node), "timeout_pool");
if (!ua->timeout_pool) {
timeout_heap_destroy(ua->timeout_heap);
socket_array_destroy(ua->sockets);
@@ -1393,17 +1480,28 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
// Показать активные таймеры
if (ua->timeout_heap) {
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
size_t deleted_timers = 0;
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
uint64_t exp = ua->timeout_heap->heap[i].expiration;
int64_t remain = (int64_t)(exp - now_ms);
if (ua->timeout_heap->heap[i].deleted) {
deleted_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer DELETED: node=%p name='%s' exp=%llu remain=%lldms",
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
} else {
active_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer: node=%p, expires=%llu ms",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer ACTIVE: node=%p name='%s' exp=%llu remain=%lldms",
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
}
}
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu", active_timers);
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu, deleted: %zu, total: %zu",
active_timers, deleted_timers, ua->timeout_heap->size);
}
// Показать активные сокеты
+11 -2
View File
@@ -86,6 +86,11 @@ uint64_t get_time_tb(void);
uint64_t get_time_us(void);
// Timeouts, timebase = 0.1 mS
// Как работать с таймаутами если таймаут может быть использован несколько раз:
// 1. заводим дескриптор таймаута и обнуляем
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
// 3. при cancel или срабатывании дескриптор обнуляем
// это обеспечит отсутствие утечек и задвоений таймаутов
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
@@ -97,8 +102,12 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
// Sockets - for socket_t (cross-platform sockets)
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, void* user_arg);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
// Динамическое управление мониторингом чтения/записи для сокета
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(struct UASYNC* ua, int timeout_tb);
+9
View File
@@ -0,0 +1,9 @@
tui enable
stepi # шаг по инструкции
nexti
step # шаг по строке исходника
next
bt
frame n
p *var
+2408
View File
File diff suppressed because it is too large. Load diff
+17 -96
View File
@@ -11,7 +11,9 @@ utun_CORE_SOURCES = \
route_bgp.c \
route_ping.c \
route_node.c \
route_node_lmdb.c \
route_connectivity.c \
conn_mgr.c \
routing.c \
tun_if.c \
tun_route.c \
@@ -20,24 +22,30 @@ 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 \
stcp_link.c \
stcp.c \
stcp_server.c \
stcp_client.c \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
control_server.c \
msg_transport.c \
firewall.c \
eim_nat.c \
nat_transport.c \
dummynet.c \
tcp_proxy.c \
proxy/tcp_proxy_client.c \
etcp_router.c \
remote_proxy.c \
udp_proxy.c \
icmp_proxy.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \
@@ -46,109 +54,22 @@ utun_CORE_SOURCES = \
# Platform-specific TUN libs (Windows only)
utun_TUN_LIBS = @TUN_LIBS@
# TinyCrypt sources (only used without OpenSSL)
utun_TINYCRYPT_SOURCES = \
$(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \
$(top_srcdir)/tinycrypt/lib/source/aes_decrypt.c \
$(top_srcdir)/tinycrypt/lib/source/cbc_mode.c \
$(top_srcdir)/tinycrypt/lib/source/ccm_mode.c \
$(top_srcdir)/tinycrypt/lib/source/cmac_mode.c \
$(top_srcdir)/tinycrypt/lib/source/ctr_mode.c \
$(top_srcdir)/tinycrypt/lib/source/ecc.c \
$(top_srcdir)/tinycrypt/lib/source/ecc_dh.c \
$(top_srcdir)/tinycrypt/lib/source/ecc_dsa.c \
$(top_srcdir)/tinycrypt/lib/source/ecc_platform_specific.c \
$(top_srcdir)/tinycrypt/lib/source/hmac.c \
$(top_srcdir)/tinycrypt/lib/source/sha256.c \
$(top_srcdir)/tinycrypt/lib/source/utils.c
# Combine sources based on OpenSSL usage
if USE_OPENSSL
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TUN_SOURCES)
else
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES) $(utun_TUN_SOURCES)
endif
# Include paths
utun_CORE_CFLAGS = \
utun_CFLAGS = \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/src/uip \
-I$(top_srcdir)/tinycrypt/lib/include \
-I$(top_srcdir)/tinycrypt/lib/source \
-g \
$(DEBUG_FLAGS)
utun_CFLAGS = $(utun_CORE_CFLAGS)
# Libraries
utun_CORE_LDADD = \
utun_LDADD = \
$(top_builddir)/lib/libuasync.a \
-lpthread \
-lm
if USE_OPENSSL
utun_LDADD = $(utun_CORE_LDADD) -lcrypto $(utun_TUN_LIBS)
else
utun_LDADD = $(utun_CORE_LDADD) $(utun_TUN_LIBS)
endif
# TinyCrypt object files (for tests that need them)
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
TINYCRYPT_OBJS = \
utun-aes_encrypt.o \
utun-aes_decrypt.o \
utun-ccm_mode.o \
utun-cmac_mode.o \
utun-ctr_mode.o \
utun-ecc.o \
utun-ecc_dh.o \
utun-ecc_dsa.o \
utun-sha256.o \
utun-ecc_platform_specific.o \
utun-utils.o
# Rules to build TinyCrypt objects (for tests)
TINYCRYPT_CFLAGS = -g -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
utun-aes_encrypt.o: $(TINYCRYPT_SRCDIR)/aes_encrypt.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-aes_decrypt.o: $(TINYCRYPT_SRCDIR)/aes_decrypt.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-ccm_mode.o: $(TINYCRYPT_SRCDIR)/ccm_mode.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-cmac_mode.o: $(TINYCRYPT_SRCDIR)/cmac_mode.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-ctr_mode.o: $(TINYCRYPT_SRCDIR)/ctr_mode.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-ecc.o: $(TINYCRYPT_SRCDIR)/ecc.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-ecc_dh.o: $(TINYCRYPT_SRCDIR)/ecc_dh.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-ecc_dsa.o: $(TINYCRYPT_SRCDIR)/ecc_dsa.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
utun-sha256.o: $(TINYCRYPT_SRCDIR)/sha256.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
# Force rebuild of ecc_platform_specific.o to pick up platform changes
utun-ecc_platform_specific.o: $(TINYCRYPT_SRCDIR)/ecc_platform_specific.c FORCE
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
FORCE:
utun-utils.o: $(TINYCRYPT_SRCDIR)/utils.c
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
# Build TinyCrypt objects for tests (convenience target)
tinycrypt-objects: $(TINYCRYPT_OBJS)
-lm \
-lcrypto \
$(utun_TUN_LIBS)
# Copy binary to project root after building
all-local: copy-to-root
+69 -46
View File
@@ -45,8 +45,9 @@ typedef enum {
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY,
SECTION_REMOTE_PROXY
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER,
SECTION_MSG_TRANSPORT
} section_type_t;
static char* trim(char *str) {
@@ -376,12 +377,8 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "inflight_min_bytes") == 0) {
global->inflight_min_bytes = atoi(value);
return 0;
}
if (strcmp(key, "inflight_max_bytes") == 0) {
global->inflight_max_bytes = atoi(value);
if (strcmp(key, "bbr_max_cwnd") == 0) {
global->bbr_max_cwnd = atoi(value);
return 0;
}
if (strcmp(key, "debug_level") == 0) {
@@ -390,6 +387,9 @@ static int parse_global(const char *key, const char *value, struct global_config
if (strcmp(key, "log_file") == 0) {
return assign_string(global->log_file, sizeof(global->log_file), value);
}
if (strcmp(key, "db_path") == 0) {
return assign_string(global->db_path, sizeof(global->db_path), value);
}
/* debug_categories key is deprecated - use [debug] section instead */
if (strcmp(key, "enable_timestamp") == 0) {
global->enable_timestamp = atoi(value);
@@ -411,7 +411,7 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, inflight_min_bytes, inflight_max_bytes, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1;
}
@@ -440,30 +440,41 @@ static int parse_control(const char *key, const char *value, struct global_confi
return -1;
}
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
static int parse_msg_transport(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "port") == 0) {
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr("127.0.0.1", port_str, &global->msg_transport_sock);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown msg_transport option '%s'. Valid: port", 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_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "tun_name") == 0) {
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
return 0;
}
if (strcmp(key, "mtu") == 0) {
global->tcp_proxy_mtu = atoi(value);
global->tcp_proxy_client_mtu = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
return -1;
}
char buf[256];
@@ -475,29 +486,29 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
char* arrow = strtok(NULL, " ");
char* remote_str = strtok(NULL, "");
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward format: %s (expected: port -> ip:port)", value);
return -1;
}
int local_port = atoi(local_port_str);
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward local port: %s", local_port_str); return -1; }
char* remote_ip_str = strtok(remote_str, ":");
char* remote_port_str = strtok(NULL, "");
if (!remote_ip_str || !remote_port_str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote: %s (expected ip:port)", remote_str);
return -1;
}
int remote_port = atoi(remote_port_str);
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote port: %s", remote_port_str); return -1; }
int idx = global->tcp_proxy_mapping_count;
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_mapping_count++;
int idx = global->tcp_proxy_client_mapping_count;
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_client_mapping_count++;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
return -1;
}
@@ -628,7 +639,12 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->only_local = atoi(value) ? 1 : 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);
if (strcmp(key, "transport") == 0) {
if (strcmp(value, "tcp") == 0) srv->transport = 1;
else srv->transport = 0; // default udp
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
return -1;
}
@@ -694,8 +710,9 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -724,8 +741,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.inflight_min_bytes = 2000; // Default 2KB
cfg->global.inflight_max_bytes = 100000; // Default 100KB
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
cfg->global.tcp_recv_buf = 0; // 0 = OS default
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
@@ -866,16 +883,22 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// parse_nat already printed the error
}
break;
case SECTION_TCP_PROXY:
cfg->global.tcp_proxy_enabled = 1;
if (parse_tcp_proxy(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy already printed the error
case SECTION_TCP_PROXY_CLIENT:
cfg->global.tcp_proxy_client_enabled = 1;
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy_client already printed the error
}
break;
case SECTION_REMOTE_PROXY:
cfg->global.remote_proxy_enabled = 1;
// remote_proxy section has no options — its presence alone enables it
(void)key; (void)value;
case SECTION_TCP_PROXY_SERVER:
cfg->global.tcp_proxy_server_enabled = 1;
if (strcmp(key, "tcp_recv_buf") == 0) {
cfg->global.tcp_recv_buf = atoi(value);
}
break;
case SECTION_MSG_TRANSPORT:
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
// parse_msg_transport already printed the error
}
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
@@ -991,9 +1014,9 @@ void print_config(const struct utun_config *cfg) {
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)",
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
ip_str_t addr_str = sockaddr_storage_to_str(&s->ip);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, addr_str.str,
s->so_mark, s->netif_index, s->type, s->mtu);
s = s->next;
}
@@ -1004,9 +1027,9 @@ void print_config(const struct utun_config *cfg) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
struct CFG_CLIENT_LINK *link = c->links;
while (link) {
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
ip_str_t addr_str = sockaddr_storage_to_str(&link->remote_addr);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s (via %s)",
addr_str.str, link->local_srv->name);
link = link->next;
}
c = c->next;
+18 -15
View File
@@ -40,6 +40,7 @@ struct CFG_SERVER {
int so_mark;
int fib; // FreeBSD FIB (routing table), -1 = don't set
uint8_t type; // public/nat/private
uint8_t transport; // 0=udp (default), 1=tcp
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
int mtu;
uint8_t only_local; // 1 = only local connections, no forwarding
@@ -78,12 +79,12 @@ struct CFG_CONTROL_ALLOW {
};
struct CFG_ALLOWED_KEY {
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
struct CFG_ALLOWED_KEY *next;
};
#define MAX_TCP_PROXY_MAPPINGS 32
struct tcp_proxy_mapping_config {
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
struct tcp_proxy_client_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
@@ -99,9 +100,11 @@ struct global_config {
int mtu;
struct sockaddr_storage control_sock;
char control_ip[MAX_ADDR_LEN]; // Control server IP address
struct sockaddr_storage msg_transport_sock;
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
char db_path[256]; // Path to LMDB nodeinfo database (empty = disabled)
char debug_level[16]; // debug level: error, warn, info, debug, trace
int enable_timestamp; // enable timestamps in logs
int enable_function_names; // enable function names in logs
@@ -118,8 +121,7 @@ struct global_config {
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200)
int inflight_min_bytes; // inflight window starting size (default: 2000)
int inflight_max_bytes; // inflight window max cap (default: 100000)
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
// Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules;
@@ -151,17 +153,18 @@ struct global_config {
} nat_forwards[MAX_NAT_FORWARDS];
int nat_forward_count;
// TCP proxy configuration ([tcp_proxy] section)
int tcp_proxy_enabled;
char tcp_proxy_tun_name[16];
char tcp_proxy_tun_ip[64];
int tcp_proxy_mtu;
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
int tcp_proxy_mapping_count;
// TCP proxy client configuration ([tcp_proxy_client] section)
int tcp_proxy_client_enabled;
char tcp_proxy_client_tun_name[16];
char tcp_proxy_client_tun_ip[64];
int tcp_proxy_client_mtu;
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
int tcp_proxy_client_mapping_count;
// Remote proxy (exit node) configuration ([remote_proxy] section)
int remote_proxy_enabled;
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
};
struct utun_config {
+3 -3
View File
@@ -15,7 +15,7 @@
#include "../lib/mem.h"
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
#define PUB_HEXKEY_LEN 129 // 64 bytes * 2 hex chars + null
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null
#define MAX_LINE_LEN 1024
@@ -41,8 +41,8 @@ static int is_valid_priv_key(const char *key) {
}
static int is_valid_pub_key(const char *key) {
if (!key || strlen(key) != 128) return 0;
for (int i = 0; i < 128; i++) {
if (!key || strlen(key) != 64) return 0;
for (int i = 0; i < 64; i++) {
if (!isxdigit((unsigned char)key[i])) return 0;
}
return 1;
+1000
View File
@@ -0,0 +1,1000 @@
#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 "../lib/ll_queue.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "etcp_router.h"
#include "config_parser.h"
#include "secure_channel.h"
#include "route_node.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "route_connectivity.h"
#include "conn_mgr.h"
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id);
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry);
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry);
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry);
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
static void cm_idle_timer_cb(void* arg);
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result);
static void cm_bg_ping_timer_cb(void* arg);
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type);
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq);
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
static int cm_has_local_addr(struct NODEINFO_Q* nq);
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry);
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq);
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry);
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CONN_MGR_INTERM_EXCHANGE_REQ* req);
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg);
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg);
static void cm_reverse_timeout_cb(void* arg);
static void cm_exchange_timeout_cb(void* arg);
static void cm_candidate_ping_timer_cb(void* arg);
struct cm_reverse_pending {
struct cm_reverse_pending* next;
uint32_t request_id;
struct CONN_MGR_ENTRY* entry;
struct ETCP_CONN** conns;
uint8_t conn_count;
};
struct cm_exchange_pending {
struct cm_exchange_pending* next;
uint32_t request_id;
struct CONN_MGR_ENTRY* entry;
void* timeout_timer;
struct CONN_MGR_INTERM_EXCHANGE_RESP cached_resp;
uint8_t resp_received;
uint8_t probes_done;
};
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: NULL instance"); return NULL; }
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR));
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: alloc failed"); return NULL; }
mgr->instance = instance;
mgr->entry_capacity = 8;
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
etcp_router_bind(instance, ETCP_ID_CONN_MGR, conn_mgr_router_recv_handler);
mgr->bg_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
mgr->candidate_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
mgr->bg_ping_cycle_start_tb = get_time_tb();
mgr->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: initialized, bg_ping started");
return mgr;
}
void conn_mgr_destroy(struct CONN_MGR* mgr) {
if (!mgr) return;
if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; }
if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; }
etcp_router_unbind(mgr->instance, ETCP_ID_CONN_MGR);
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
u_free(mgr->entries);
mgr->initialized = 0;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count);
u_free(mgr);
}
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) {
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
entry->state = CONN_MGR_STATE_DISCONNECTED;
entry->conn_type = CONN_TYPE_NONE;
}
static struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id) {
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
if (e) return e;
if (mgr->entry_count >= mgr->entry_capacity) {
size_t new_cap = mgr->entry_capacity * 2;
struct CONN_MGR_ENTRY* new_e = u_realloc(mgr->entries, new_cap * sizeof(struct CONN_MGR_ENTRY));
if (!new_e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; }
memset(new_e + mgr->entry_capacity, 0, (new_cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY));
mgr->entries = new_e;
mgr->entry_capacity = new_cap;
}
struct CONN_MGR_ENTRY* new_entry = &mgr->entries[mgr->entry_count++];
memset(new_entry, 0, sizeof(*new_entry));
new_entry->node_id = node_id;
new_entry->mgr = mgr;
return new_entry;
}
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) {
for (size_t i = 0; i < mgr->entry_count; i++)
if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED)
return &mgr->entries[i];
return NULL;
}
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result) {
struct cm_cb_node* node = entry->cb_list;
while (node) { struct cm_cb_node* next = node->next; node->cb(result, entry->node_id, node->arg); u_free(node); node = next; }
entry->cb_list = NULL;
}
static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
if (!bgp) return;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, entry->node_id);
if (!nq) return;
nq->conn_mgr_type = entry->conn_type;
memcpy(nq->conn_mgr_intermediaries, entry->intermediaries, sizeof(entry->intermediaries));
nq->conn_mgr_intermediariy_count = entry->intermediariy_count;
}
static void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, node_id);
if (!nq) return;
nq->conn_mgr_type = CONN_TYPE_NONE;
nq->conn_mgr_intermediariy_count = 0;
}
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms,
conn_mgr_connect_callback_t cb, void* cb_arg) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: BGP not initialized"); return CONN_MGR_ERR_INTERNAL; }
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, node_id);
if (!target) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx not found", (unsigned long long)node_id); return CONN_MGR_ERR_NOT_FOUND; }
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (entry && entry->state == CONN_MGR_STATE_CONNECTED) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connected", (unsigned long long)node_id);
return CONN_MGR_ERR_ALREADY_CONNECTED;
}
if (entry && entry->state == CONN_MGR_STATE_CONNECTING) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connecting, adding cb", (unsigned long long)node_id);
struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; }
return CONN_MGR_OK;
}
struct ETCP_CONN* existing = route_bgp_find_conn_for_node(bgp, node_id);
if (existing && existing->peer_node_id == node_id && existing->links) {
struct ETCP_LINK* l = existing->links;
while (l) { if (l->link_status && l->initialized) break; l = l->next; }
if (l) {
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTED;
entry->conn_type = CONN_TYPE_DIRECT;
entry->idle_timeout_ms = idle_timeout_ms;
entry->alien = target->alien;
entry->last_traffic_tb = get_time_tb();
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } }
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
return CONN_MGR_OK;
}
}
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTING;
entry->alien = target->alien;
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } }
entry->idle_timeout_ms = idle_timeout_ms;
entry->last_traffic_tb = get_time_tb();
entry->local_scan_state = CM_TRY_NONE;
entry->main_connect_state = CM_TRY_NONE;
entry->main.phase = 0;
entry->main.timer = NULL;
entry->main.request_id = 0;
int has_our_local = cm_has_local_addr(bgp->local_node);
int has_target_local = cm_has_local_addr(target);
if (has_our_local && has_target_local) { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); }
entry->main_connect_state = CM_TRY_PENDING;
cm_start_phase_direct(entry);
return CONN_MGR_OK;
}
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
struct CONN_MGR_DISCONNECT pkt;
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT;
pkt.node_id = node_id;
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(mgr->instance, node_id, qe, 1); }
cm_clear_nodeinfo(mgr, node_id);
cm_entry_destroy(entry);
entry->state = CONN_MGR_STATE_DISCONNECTED;
entry->conn_type = CONN_TYPE_NONE;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: disconnected node 0x%016llx", (unsigned long long)node_id);
return CONN_MGR_OK;
}
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
entry->idle_timeout_ms = timeout_ms;
if (entry->state == CONN_MGR_STATE_CONNECTED && timeout_ms > 0 && !entry->idle_timer)
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
if (timeout_ms == 0 && entry->idle_timer) { uasync_cancel_timeout(mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
return CONN_MGR_OK;
}
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_state, uint8_t* out_conn_type) {
if (!mgr || !out_state || !out_conn_type) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) { *out_state = CONN_MGR_STATE_DISCONNECTED; *out_conn_type = CONN_TYPE_NONE; return CONN_MGR_OK; }
*out_state = entry->state;
*out_conn_type = entry->conn_type;
return CONN_MGR_OK;
}
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) {
if (!mgr || !nodeinfo_data || len < sizeof(struct NODEINFO)) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) return CONN_MGR_ERR_INTERNAL;
const struct NODEINFO* ni = (const struct NODEINFO*)nodeinfo_data;
uint64_t node_id = ni->node_id;
struct NODEINFO_Q* existing = nodeinfo_find_by_id(bgp, node_id);
if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; }
size_t dyn_sz = len - sizeof(struct NODEINFO);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node alloc failed"); return CONN_MGR_ERR_INTERNAL; }
memcpy(&nq->node, nodeinfo_data, len);
nq->alien = 1;
nq->dirty = 0;
nq->last_ver = ni->ver;
nq->connectivity.ping_req_time = 0;
if (bgp->nodes) queue_data_put_with_index(bgp->nodes, &nq->ll);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id);
return CONN_MGR_OK;
}
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) {
if (!mgr || !entry) return -1;
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; }
e->last_traffic_tb = get_time_tb();
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_ID_CONN_MGR);
if (!rconn) { queue_entry_free(entry); return -1; }
int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len);
queue_entry_free(entry);
return ret;
}
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt) {
if (!mgr || node_id == mgr->instance->node_id || rtt == 0) return;
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
if (mgr->best_candidates[i].node_id == node_id) {
mgr->best_candidates[i].rtt = rtt;
for (uint8_t j = i; j > 0 && mgr->best_candidates[j].rtt < mgr->best_candidates[j-1].rtt; j--) {
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
mgr->best_candidates[j] = mgr->best_candidates[j-1];
mgr->best_candidates[j-1] = tmp;
}
for (uint8_t j = i; j + 1 < mgr->best_candidate_count && mgr->best_candidates[j].rtt > mgr->best_candidates[j+1].rtt; j++) {
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
mgr->best_candidates[j] = mgr->best_candidates[j+1];
mgr->best_candidates[j+1] = tmp;
}
return;
}
}
if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES) {
mgr->best_candidates[mgr->best_candidate_count].node_id = node_id;
mgr->best_candidates[mgr->best_candidate_count].rtt = rtt;
mgr->best_candidate_count++;
} else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt) {
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].node_id = node_id;
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt = rtt;
} else return;
for (uint8_t i = mgr->best_candidate_count - 1; i > 0; i--) {
if (mgr->best_candidates[i].rtt >= mgr->best_candidates[i-1].rtt) break;
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[i];
mgr->best_candidates[i] = mgr->best_candidates[i-1];
mgr->best_candidates[i-1] = tmp;
}
}
static void cm_idle_timer_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
struct CONN_MGR* mgr = entry->mgr;
uint64_t now_tb = get_time_tb();
if (entry->state != CONN_MGR_STATE_CONNECTED || entry->idle_timeout_ms == 0) return;
uint64_t idle = now_tb - entry->last_traffic_tb;
if (idle > (uint64_t)entry->idle_timeout_ms * 10) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting",
(unsigned long long)entry->node_id, (unsigned long long)(idle / 10));
conn_mgr_disconnect_node(mgr, entry->node_id);
return;
}
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
}
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type) {
uint8_t tt = target_meta_type;
if (tt == NAT_VERIFIED_DIRECT) tt = CFG_SERVER_TYPE_PUBLIC;
else if (tt >= NAT_VERIFIED_UNKNOWN) tt = CFG_SERVER_TYPE_PUBLIC;
uint8_t ot = our->type;
if (ot == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
if (ot == CFG_SERVER_TYPE_PRIVATE && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
return 0;
}
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq) {
uint16_t rtt = 0xFFFF;
if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < rtt)
rtt = nq->connectivity.interface_min_rtt;
if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < rtt)
rtt = nq->connectivity.nat_min_rtt;
if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < rtt)
rtt = nq->connectivity.real_min_rtt;
return rtt;
}
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq) {
uint64_t t = nq->connectivity.interface_probe_time;
if (nq->connectivity.nat_probe_time > t) t = nq->connectivity.nat_probe_time;
if (nq->connectivity.real_probe_time > t) t = nq->connectivity.real_probe_time;
return t;
}
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb) {
uint64_t last = cm_get_node_max_probe_time(nq);
return (last > 0 && (now_tb - last) < (uint64_t)(CONN_MGR_CANDIDATE_CACHE_MS * 10));
}
static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT))
return 1;
}
}
}
return 0;
}
static int cm_has_local_addr(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id && metas[j].type == CFG_SERVER_TYPE_PRIVATE)
return 1;
}
}
}
return 0;
}
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
static void cm_direct_timeout_cb(void* arg);
struct cm_ping_ctx {
struct CONN_MGR_ENTRY* entry;
struct sockaddr_storage addr;
struct ETCP_SOCKET* sock;
uint8_t phase; /* 0=local_scan, 1=direct */
uint8_t attempt;
};
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { entry->local_scan_state = CM_TRY_FAILED; return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id || metas[j].type != CFG_SERVER_TYPE_PRIVATE) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PRIVATE && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa));
memcpy(&sa, &sin, sizeof(sin));
struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx));
if (!ctx) continue;
ctx->entry = entry;
ctx->addr = sa;
ctx->sock = s;
ctx->phase = 0;
ctx->attempt = 0;
etcp_send_ping_to_socket(entry->mgr->instance, s, target->node.public_key, &sa,
CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
return;
}
s = s->next;
}
}
}
entry->local_scan_state = CM_TRY_FAILED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan no candidates for 0x%016llx", (unsigned long long)entry->node_id);
}
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
if (entry->main_connect_state == CM_TRY_OK) return;
if (entry->local_scan_state == CM_TRY_OK) return;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
uint8_t priority_order[] = { ADDR_TYPE_REAL, ADDR_TYPE_NAT, ADDR_TYPE_INTERFACE };
for (int pri = 0; pri < 3; pri++) {
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != priority_order[pri]) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
int compat = cm_nat_compatible(s, metas[j].type);
if (compat && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa));
memcpy(&sa, &sin, sizeof(sin));
struct ETCP_CONN* conn = etcp_connection_create(entry->mgr->instance, NULL);
if (conn) {
etcp_conn_set_ready_cbk(conn, cm_direct_ready_cb, entry);
sc_set_peer_public_key(&conn->crypto_ctx, target->node.public_key, 0);
struct ETCP_LINK* link = etcp_link_new(conn, s, &sa, 0);
if (link) {
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua,
CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS * 10, entry, cm_direct_timeout_cb, "conn_mgr_direct");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) started INIT to 0x%016llx",
(unsigned long long)entry->node_id);
return;
}
}
}
s = s->next;
}
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) no compatible pairs for 0x%016llx",
(unsigned long long)entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = cm_has_direct_ip(target);
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
cm_start_phase_indirect(entry);
}
static void cm_direct_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = target ? cm_has_direct_ip(target) : 0;
if (entry->local_scan_state != CM_TRY_OK) {
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
cm_start_phase_indirect(entry);
return;
}
if (entry->local_scan_state == CM_TRY_FAILED && entry->main_connect_state == CM_TRY_FAILED)
cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE);
}
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
if (!conn || conn->peer_node_id != entry->node_id) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready cb peer mismatch conn=%p peer=0x%llx entry=0x%llx",
(void*)conn, (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready for 0x%016llx", (unsigned long long)entry->node_id);
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
entry->main_connect_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_DIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
}
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len) {
struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg;
struct CONN_MGR_ENTRY* entry = ctx->entry;
(void)nonce; (void)resp_data; (void)resp_data_len;
if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; }
if (success && ctx->phase == 0) {
entry->local_scan_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_DIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt);
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
u_free(ctx);
return;
}
ctx->attempt++;
if (ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) {
etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock,
nodeinfo_find_by_id(entry->mgr->instance->bgp, entry->node_id)->node.public_key,
&ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s failed for 0x%016llx after %d attempts",
ctx->phase == 0 ? "local scan" : "direct", (unsigned long long)entry->node_id, ctx->attempt);
if (ctx->phase == 0) entry->local_scan_state = CM_TRY_FAILED;
u_free(ctx);
}
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* local = bgp->local_node;
if (!local) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
uint32_t req_id = ++entry->mgr->next_request_id;
entry->main.request_id = req_id;
entry->main.phase = 2;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(local, &addrs);
int meta_count = get_node_v4_sockets_meta(local, &metas);
uint8_t direct_count = 0;
struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8];
for (int i = 0; i < addr_count && direct_count < 8; i++) {
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT)) {
out_addrs[direct_count].type = addrs[i].type;
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
out_addrs[direct_count].port = htons(addrs[i].port);
out_addrs[direct_count].socket_id = addrs[i].socket_id;
direct_count++;
break;
}
}
}
if (direct_count == 0) { cm_start_phase_indirect(entry); return; }
size_t pkt_size = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)direct_count * 8;
uint8_t* pkt = u_malloc(pkt_size);
if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt;
req->cmd = ETCP_ID_CONN_MGR;
req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ;
req->request_id = req_id;
req->addr_count = direct_count;
uint8_t* p = pkt + CONN_MGR_DIRECT_REQ_HDR_SIZE;
for (uint8_t i = 0; i < direct_count; i++) {
*p++ = out_addrs[i].type;
memcpy(p, out_addrs[i].ip, 4); p += 4;
memcpy(p, &out_addrs[i].port, 2); p += 2;
*p++ = out_addrs[i].socket_id;
}
struct ll_entry* qe = queue_entry_new(pkt_size);
if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
memcpy(qe->data, pkt, pkt_size);
qe->len = (uint16_t)pkt_size;
u_free(pkt);
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
if (rp) { rp->request_id = req_id; rp->entry = entry; rp->next = entry->mgr->reverse_pending;
entry->mgr->reverse_pending = rp; }
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10,
entry, cm_reverse_timeout_cb, "conn_mgr_reverse");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs",
(unsigned long long)entry->node_id, direct_count);
}
static void cm_reverse_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; }
cm_start_phase_indirect(entry);
}
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg;
struct CONN_MGR_ENTRY* entry = rp->entry;
if (!conn || conn->peer_node_id != entry->node_id) { u_free(rp); return; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id);
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; }
entry->main_connect_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_REVERSE;
entry->state = CONN_MGR_STATE_CONNECTED;
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
u_free(rp);
}
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
struct CONN_MGR* mgr = entry->mgr;
if (mgr->best_candidate_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx",
(unsigned long long)entry->node_id);
return CONN_MGR_ERR_UNREACHABLE;
}
uint32_t req_id = ++mgr->next_request_id;
entry->main.request_id = req_id;
entry->main.phase = 3;
struct CONN_MGR_INTERM_EXCHANGE_REQ req;
memset(&req, 0, sizeof(req));
req.cmd = ETCP_ID_CONN_MGR;
req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ;
req.request_id = req_id;
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
for (uint8_t i = 0; i < req.candidate_count; i++)
req.candidates[i] = mgr->best_candidates[i];
struct ll_entry* qe = queue_entry_new(sizeof(req));
if (!qe) return CONN_MGR_ERR_INTERNAL;
memcpy(qe->data, &req, sizeof(req));
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10,
entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch");
{
struct cm_exchange_pending* ep = u_calloc(1, sizeof(struct cm_exchange_pending));
if (ep) { ep->request_id = req_id; ep->entry = entry; ep->timeout_timer = entry->main.timer;
ep->next = mgr->exchange_pending; mgr->exchange_pending = ep; }
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) sent EXCHANGE_REQ to 0x%016llx with %u candidates",
(unsigned long long)entry->node_id, req.candidate_count);
return CONN_MGR_OK;
}
static void cm_exchange_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct cm_exchange_pending** pp = &entry->mgr->exchange_pending;
while (*pp) { if ((*pp)->entry == entry) { struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); break; } pp = &(*pp)->next; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id);
cm_deliver_result(entry, CONN_MGR_ERR_TIMEOUT);
}
static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_MGR_INTERM_EXCHANGE_RESP* resp) {
struct { uint64_t node_id; uint64_t total_rtt; } all[8];
uint8_t count = 0;
for (uint8_t i = 0; i < resp->your_count && count < 8; i++) {
uint64_t id = resp->your_candidates[i].node_id;
uint16_t our_rtt = 0;
for (uint8_t j = 0; j < CONN_MGR_MAX_CANDIDATES; j++) {
if (entry->mgr->best_candidates[j].node_id == id) { our_rtt = entry->mgr->best_candidates[j].rtt; break; }
}
all[count].node_id = id;
all[count].total_rtt = (uint64_t)our_rtt + resp->your_candidates[i].rtt;
count++;
}
for (uint8_t i = 0; i < resp->my_count && count < 8; i++) {
uint64_t id = resp->my_candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, id);
uint16_t our_rtt = nq ? cm_get_node_min_rtt(nq) : 0;
if (our_rtt == 0xFFFF) our_rtt = 0;
all[count].node_id = id;
all[count].total_rtt = (uint64_t)our_rtt + resp->my_candidates[i].rtt;
count++;
}
for (uint8_t i = 0; i < count; i++)
for (uint8_t j = i + 1; j < count; j++)
if (all[j].total_rtt < all[i].total_rtt) { typeof(all[0]) t = all[i]; all[i] = all[j]; all[j] = t; }
uint8_t sel = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count;
for (uint8_t i = 0; i < sel; i++) entry->intermediaries[i] = all[i].node_id;
entry->intermediariy_count = sel;
entry->rr_idx = 0;
struct CONN_MGR_INTERM_SELECTED pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED;
pkt.request_id = 0;
pkt.count = sel;
for (uint8_t i = 0; i < sel; i++) {
pkt.selected[i].node_id = all[i].node_id;
pkt.selected[i].rtt = 0;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, all[i].node_id);
if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq);
}
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1); }
entry->conn_type = CONN_TYPE_INDIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect OK for 0x%016llx via %u intermediaries",
(unsigned long long)entry->node_id, sel);
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
}
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data;
size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates);
if (len < min_size) return;
struct cm_exchange_pending* ep = mgr->exchange_pending;
while (ep) { if (ep->request_id == resp->request_id) break; ep = ep->next; }
if (!ep) return;
if (ep->entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, ep->entry->main.timer); ep->entry->main.timer = NULL; }
memcpy(&ep->cached_resp, resp, len < sizeof(ep->cached_resp) ? len : sizeof(ep->cached_resp));
ep->resp_received = 1;
ep->probes_done = 0;
uint8_t need_probe = 0;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, ep->cached_resp.my_candidates[i].node_id);
if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; route_connectivity_probe_node(mgr->instance, nq); }
}
if (need_probe == 0) { ep->probes_done = 1; cm_compute_intermediaries(ep->entry, &ep->cached_resp);
struct cm_exchange_pending** pp = &mgr->exchange_pending;
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
u_free(ep); return; }
uasync_set_timeout(mgr->instance->ua, 2000, ep, NULL, "cm_exch_probe");
}
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
const struct CONN_MGR_INTERM_SELECTED* sel = (const struct CONN_MGR_INTERM_SELECTED*)data;
if (len < offsetof(struct CONN_MGR_INTERM_SELECTED, selected)) return;
struct CONN_MGR_ENTRY* entry = NULL;
for (size_t i = 0; i < mgr->entry_count; i++)
if (mgr->entries[i].state == CONN_MGR_STATE_CONNECTING) { entry = &mgr->entries[i]; break; }
if (!entry) return;
uint8_t cnt = sel->count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : sel->count;
for (uint8_t i = 0; i < cnt; i++) entry->intermediaries[i] = sel->selected[i].node_id;
entry->intermediariy_count = cnt;
entry->rr_idx = 0;
entry->conn_type = CONN_TYPE_INDIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got INTERM_SELECTED count=%u", cnt);
}
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < 3) return;
uint8_t* dgram = entry->dgram + 1; // skip svc_id byte from router
size_t len = entry->len - 1;
uint8_t subcmd = dgram[1];
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (mgr) {
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id);
if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb();
}
switch (subcmd) {
case CONN_MGR_SUBCMD_DIRECT_REQ:
cm_handle_direct_req(conn, dgram, len);
break;
case CONN_MGR_SUBCMD_DIRECT_RESP:
break;
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ:
if (len >= CONN_MGR_INTERM_EXCHANGE_REQ_SIZE)
cm_handle_interm_exchange_req(conn, (struct CONN_MGR_INTERM_EXCHANGE_REQ*)dgram);
break;
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP:
if (mgr) cm_handle_interm_exchange_resp(mgr, dgram, len);
break;
case CONN_MGR_SUBCMD_INTERM_SELECTED:
if (mgr) cm_handle_interm_selected(mgr, dgram, len);
break;
case CONN_MGR_SUBCMD_DISCONNECT:
if (len >= CONN_MGR_DISCONNECT_SIZE && mgr) {
struct CONN_MGR_DISCONNECT* disc = (struct CONN_MGR_DISCONNECT*)dgram;
cm_handle_disconnect(mgr, disc->node_id);
}
break;
}
}
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) {
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (!mgr) return;
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)data;
size_t expected = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)req->addr_count * 8;
if (len < expected) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs",
(unsigned long long)conn->peer_node_id, req->addr_count);
uint8_t* p = (uint8_t*)data + CONN_MGR_DIRECT_REQ_HDR_SIZE;
uint64_t src_node_id = conn->peer_node_id;
for (uint8_t i = 0; i < req->addr_count; i++) {
uint8_t type = *p++;
uint8_t ip[4]; memcpy(ip, p, 4); p += 4;
uint16_t port; memcpy(&port, p, 2); p += 2;
uint8_t sock_id = *p++;
(void)sock_id; (void)type;
struct ETCP_SOCKET* s = conn->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PUBLIC && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port;
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin));
struct ETCP_CONN* new_conn = etcp_connection_create(conn->instance, NULL);
if (new_conn) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(conn->instance->bgp, src_node_id);
if (nq) sc_set_peer_public_key(&new_conn->crypto_ctx, nq->node.public_key, 0);
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
if (rp) { rp->request_id = req->request_id; rp->entry = NULL; rp->next = mgr->reverse_pending;
etcp_conn_set_ready_cbk(new_conn, cm_reverse_ready_cb, rp); mgr->reverse_pending = rp; }
etcp_link_new(new_conn, s, &sa, 0);
}
break;
}
s = s->next;
}
}
}
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn,
struct CONN_MGR_INTERM_EXCHANGE_REQ* req) {
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (!mgr) return;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < req->candidate_count && i < 4; i++) {
uint64_t cand_id = req->candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
if (nq && cm_is_rtt_fresh(nq, now_tb)) continue;
route_connectivity_probe_node(mgr->instance, nq);
}
struct CONN_MGR_INTERM_EXCHANGE_RESP resp;
memset(&resp, 0, sizeof(resp));
resp.cmd = ETCP_ID_CONN_MGR;
resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP;
resp.request_id = req->request_id;
resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
for (uint8_t i = 0; i < resp.my_count; i++)
resp.my_candidates[i] = mgr->best_candidates[i];
resp.your_count = req->candidate_count > 4 ? 4 : req->candidate_count;
for (uint8_t i = 0; i < resp.your_count; i++) {
resp.your_candidates[i].node_id = req->candidates[i].node_id;
struct NODEINFO_Q* nq2 = nodeinfo_find_by_id(mgr->instance->bgp, req->candidates[i].node_id);
uint16_t rtt = nq2 ? cm_get_node_min_rtt(nq2) : 0;
resp.your_candidates[i].rtt = rtt;
}
size_t resp_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates) +
(size_t)resp.your_count * sizeof(struct CONN_MGR_CANDIDATE);
struct ll_entry* qe = queue_entry_new(resp_size);
if (qe) {
memcpy(qe->data, &resp, resp_size);
etcp_route_send(mgr->instance, conn->peer_node_id, qe, 1);
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",
(unsigned long long)conn->peer_node_id, resp.my_count, resp.your_count);
}
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) {
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return;
cm_clear_nodeinfo(mgr, node_id);
cm_entry_destroy(entry);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id);
}
static void cm_bg_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp || !bgp->nodes) {
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
uint64_t now_tb = get_time_tb();
struct ll_entry* e = bgp->nodes->head;
size_t total = 0;
while (e) { total++; e = e->next; }
if (total == 0) {
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
if (mgr->bg_ping_cursor >= total) {
mgr->bg_ping_cursor = 0;
if ((now_tb - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB) {
uint64_t delay = CONN_MGR_BG_PING_CYCLE_MIN_TB - (now_tb - mgr->bg_ping_cycle_start_tb);
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)delay, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
mgr->bg_ping_cycle_start_tb = now_tb;
}
size_t cursor = 0;
e = bgp->nodes->head;
while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; }
if (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
uint64_t node_id = nq->node.node_id;
if (node_id != mgr->instance->node_id) {
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(bgp, node_id);
int has_active = 0;
if (dconn && dconn->links) {
struct ETCP_LINK* l = dconn->links;
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
}
if (!has_active) {
etcp_send_ping(mgr->instance, nq->node.public_key, NULL, CONN_PROBE_TIMEOUT_MS,
NULL, NULL, NULL, 0);
}
}
mgr->bg_ping_cursor++;
}
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
}
static void cm_candidate_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
uint64_t cand_id = mgr->best_candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
if (!nq) {
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
mgr->best_candidate_count--; i--; continue;
}
uint64_t last = cm_get_node_max_probe_time(nq);
if (last > 0 && (now_tb - last) >= CONN_MGR_CANDIDATE_STALE_TB) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)cand_id);
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
mgr->best_candidate_count--; i--; continue;
}
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(mgr->instance->bgp, cand_id);
int has_active = 0;
if (dconn && dconn->links) {
struct ETCP_LINK* l = dconn->links;
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
}
if (has_active)
etcp_send_ping(mgr->instance, nq->node.public_key, &dconn->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0);
else
route_connectivity_probe_node(mgr->instance, nq);
}
mgr->candidate_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_CANDIDATE_PING_TB,
mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
}
+190
View File
@@ -0,0 +1,190 @@
#ifndef CONN_MGR_H
#define CONN_MGR_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "route_node.h"
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct NODEINFO_Q;
struct ROUTE_BGP;
#define CONN_MGR_MAX_CANDIDATES 3
#define CONN_MGR_CANDIDATE_CACHE_MS 20000
#define CONN_MGR_CANDIDATE_STALE_TB 300000
#define CONN_MGR_CANDIDATE_PING_TB 20000
#define CONN_MGR_BG_PING_INTERVAL_TB 1000
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000
#define CONN_MGR_OK 0
#define CONN_MGR_ERR_NOT_FOUND -1
#define CONN_MGR_ERR_NO_ADDRESSES -2
#define CONN_MGR_ERR_TIMEOUT -3
#define CONN_MGR_ERR_UNREACHABLE -4
#define CONN_MGR_ERR_REFUSED -5
#define CONN_MGR_ERR_ALREADY_CONNECTED -6
#define CONN_MGR_ERR_INTERNAL -7
enum CONN_MGR_STATE {
CONN_MGR_STATE_DISCONNECTED = 0,
CONN_MGR_STATE_CONNECTING = 1,
CONN_MGR_STATE_CONNECTED = 2,
};
enum CM_TRY_STATE {
CM_TRY_NONE = 0,
CM_TRY_PENDING = 1,
CM_TRY_OK = 2,
CM_TRY_FAILED = 3,
};
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05
#define CONN_MGR_SUBCMD_DISCONNECT 0x06
struct CONN_MGR_CANDIDATE {
uint64_t node_id;
uint16_t rtt;
} __attribute__((packed));
#pragma pack(push, 1)
struct CONN_MGR_DIRECT_REQ {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t addr_count;
};
struct CONN_MGR_DIRECT_RESP {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t accepted;
};
struct CONN_MGR_INTERM_EXCHANGE_REQ {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t candidate_count;
struct CONN_MGR_CANDIDATE candidates[4];
};
struct CONN_MGR_INTERM_EXCHANGE_RESP {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t my_count;
uint8_t your_count;
struct CONN_MGR_CANDIDATE my_candidates[4];
struct CONN_MGR_CANDIDATE your_candidates[4];
};
struct CONN_MGR_INTERM_SELECTED {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t count;
struct CONN_MGR_CANDIDATE selected[3];
};
struct CONN_MGR_DISCONNECT {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
};
#pragma pack(pop)
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ)
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP)
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ)
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP)
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED)
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT)
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
struct cm_cb_node {
conn_mgr_connect_callback_t cb;
void* arg;
struct cm_cb_node* next;
};
struct CONN_MGR_ENTRY {
uint64_t node_id;
uint8_t state;
uint8_t conn_type;
uint8_t alien;
uint32_t idle_timeout_ms;
uint64_t last_traffic_tb;
void* idle_timer;
struct cm_cb_node* cb_list;
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES];
uint8_t intermediariy_count;
uint8_t rr_idx;
uint8_t local_scan_state;
uint8_t main_connect_state;
struct {
uint8_t phase;
void* timer;
uint32_t request_id;
void* conn_ctx;
} main;
struct CONN_MGR* mgr;
};
struct CONN_MGR {
struct UTUN_INSTANCE* instance;
struct CONN_MGR_ENTRY* entries;
size_t entry_count;
size_t entry_capacity;
void* bg_ping_timer;
size_t bg_ping_cursor;
uint64_t bg_ping_cycle_start_tb;
void* candidate_ping_timer;
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES];
uint8_t best_candidate_count;
uint32_t next_request_id;
uint8_t initialized;
struct cm_reverse_pending* reverse_pending;
struct cm_exchange_pending* exchange_pending;
};
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
void conn_mgr_destroy(struct CONN_MGR* mgr);
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id,
uint32_t idle_timeout_ms,
conn_mgr_connect_callback_t cb, void* cb_arg);
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id,
uint8_t* out_state, uint8_t* out_conn_type);
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
#endif
+262 -38
View File
@@ -11,6 +11,7 @@
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "route_node.h"
#include "pkt_normalizer.h"
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/u_async.h"
@@ -93,10 +94,19 @@ static void close_client(struct control_server* server, struct control_client* c
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq);
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len);
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
/* ETCP ID/subcmd constants for BGP_NODEINFO_PACKET (from route_bgp.h) */
#define CTRL_BGP_CMD 0x01
#define CTRL_BGP_SUBCMD 0x04
/* ============================================================================
* Server Initialization
* ============================================================================ */
@@ -399,13 +409,26 @@ static void accept_callback(socket_t fd, void* arg) {
client->fd = client_fd;
client->server = server; /* Store back pointer to server */
// client->connected = 1;
client->selected_peer_id = 0;
client->output_queue = queue_new(server->ua, 0, 0, 0, "ctrl_out");
if (!client->output_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to create output queue for client");
u_free(client);
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
client->subscribed_nodes = 0;
client->write_registered = 0;
/* Register with uasync */
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
client_read_callback,
NULL, /* write callback - not needed for now */
client_write_callback,
client_except_callback,
client);
@@ -543,9 +566,44 @@ static void client_read_callback(socket_t fd, void* arg) {
}
static void client_write_callback(socket_t fd, void* arg) {
/* Not used for now - writes are immediate */
struct control_client* client = (struct control_client*)arg;
struct control_server* server = client->server;
struct ll_queue* q = client->output_queue;
while (q->head) {
struct ll_entry* entry = q->head;
#ifdef _WIN32
int sent = send(fd, (const char*)entry->dgram, entry->len, 0);
if (sent == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %d, closing", err);
close_client(server, client);
return;
}
return;
}
#else
ssize_t sent = send(fd, entry->dgram, entry->len, 0);
if (sent < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %s, closing", strerror(errno));
close_client(server, client);
return;
}
return;
}
#endif
if ((size_t)sent < entry->len) { entry->dgram += sent; entry->len -= (uint16_t)sent; q->total_bytes -= (size_t)sent; return; }
struct ll_entry* e = queue_data_get(q);
queue_dgram_free(e); queue_entry_free(e);
}
uasync_set_socket_write(server->ua, client->socket_id, 0);
client->write_registered = 0;
(void)fd;
(void)arg;
}
static void client_except_callback(socket_t fd, void* arg) {
@@ -559,6 +617,21 @@ static void client_except_callback(socket_t fd, void* arg) {
(void)fd;
}
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len) {
(void)server;
if (!client || !client->output_queue || !dgram || len == 0) return -1;
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate output queue entry"); return -1; }
entry->dgram = dgram;
entry->len = len;
entry->int_len = len;
queue_data_put(client->output_queue, entry);
if (!client->write_registered) {
uasync_set_socket_write(client->server->ua, client->socket_id, 1);
client->write_registered = 1;
}
return 0;
}
static void close_client(struct control_server* server, struct control_client* client) {
if (!server || !client) return;
@@ -582,7 +655,9 @@ static void close_client(struct control_server* server, struct control_client* c
close(client->fd);
}
#endif
if (client->output_queue) { queue_free(client->output_queue); client->output_queue = NULL; }
/* Remove from list */
struct control_client** curr = &server->clients;
while (*curr) {
@@ -613,7 +688,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_ACTION) {
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
@@ -696,6 +771,41 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_GET_DEBUG_CONFIG:
send_debug_config(server, client, req_seq);
break;
case ETCPMON_CMD_SET_DEBUG_CONFIG:
if (payload_size >= 2) {
uint8_t global_lvl = payload[0];
uint8_t cat_count = payload[1];
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
const uint8_t* levels = payload + 2;
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
if (levels[i] <= DEBUG_LEVEL_TRACE)
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
fflush(server->log_file);
}
}
break;
case ETCPMON_CMD_SUBSCRIBE_NODES:
client->subscribed_nodes = 1;
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
(unsigned long long)get_timestamp_ms());
fflush(server->log_file);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
control_server_send_node_list(server, client, req_seq);
break;
case ETCPMON_CMD_DISCONNECT:
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
@@ -770,13 +880,7 @@ static void send_conn_list(struct control_server* server, struct control_client*
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);
control_client_send(server, client, buffer, rsp_size);
}
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
@@ -794,8 +898,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
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);
control_client_send(server, client, buffer, rsp_size);
return;
}
@@ -864,17 +967,11 @@ static void send_socket_list(struct control_server* server, struct control_clien
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);
(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);
control_client_send(server, client, buffer, rsp_size);
}
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
@@ -1068,7 +1165,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].bandwidth = link->bandwidth;
link_info[i].nat_changes_count = link->nat_changes_count;
link_info[i].rtt_last = link->rtt_last;
link_info[i].rtt_avg10 = link->rtt_avg10;
link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
link_info[i].tt_last = link->tt_last;
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;
@@ -1078,6 +1175,25 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].inflight_bytes = link->inflight_bytes;
link_info[i].inflight_packets = link->inflight_packets;
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
/* BBR fields */
if (link->bbr) {
link_info[i].bbr_mode = link->bbr->mode;
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
} else {
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
}
link = link->next;
}
@@ -1092,13 +1208,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
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);
control_client_send(server, client, buffer, rsp_size);
}
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
@@ -1126,13 +1236,46 @@ static void send_error(struct control_client* client, uint8_t error_code, const
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
control_client_send(server, client, buffer, rsp_size);
}
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);
}
control_client_send(server, client, buffer, rsp_size);
}
/* ============================================================================
@@ -1151,6 +1294,87 @@ static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instan
return NULL;
}
/* ============================================================================
* Node Info Helpers (send_single_node_info, send_node_list, notify_change)
* ============================================================================ */
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq) {
int dyn = nodeinfo_dyn_size(&nq->node);
size_t np_size = sizeof(struct etcpmon_msg_header) + 2 + sizeof(struct NODEINFO) + dyn;
uint8_t* buffer = (uint8_t*)u_malloc(np_size);
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer"); return; }
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, 2 + sizeof(struct NODEINFO) + dyn, ETCPMON_RSP_NODE_INFO, 0);
uint8_t* pay = buffer + sizeof(*hdr);
pay[0] = CTRL_BGP_CMD; pay[1] = CTRL_BGP_SUBCMD;
memcpy(pay + 2, &nq->node, sizeof(struct NODEINFO));
memcpy(pay + 2 + sizeof(struct NODEINFO), (uint8_t*)&nq->node + sizeof(struct NODEINFO), dyn);
control_client_send(server, client, buffer, (uint16_t)np_size);
}
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct ROUTE_BGP* bgp = server->instance->bgp;
if (!bgp) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
/* Send local node first */
if (bgp->local_node) send_single_node_info(server, client, bgp->local_node);
/* Send all known remote nodes */
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
send_single_node_info(server, client, nq);
e = e->next;
}
/* Send end-of-list marker */
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header));
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, 0, ETCPMON_RSP_NODE_INFO_END, seq_id);
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Sent node list to client (seq=%d)", seq_id);
}
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node) {
if (!server || !node) return;
struct control_client* client = server->clients;
while (client) {
if (client->subscribed_nodes) send_single_node_info(server, client, node);
client = client->next;
}
}
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id) {
if (!server) return;
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(uint64_t), ETCPMON_RSP_NODE_REMOVED, 0);
*(uint64_t*)(buffer + sizeof(*hdr)) = node_id;
struct control_client* client = server->clients;
while (client) {
if (client->subscribed_nodes) {
/* Every client needs its own copy since ownership transfers to the queue */
uint8_t* copy = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
if (copy) {
memcpy(copy, buffer, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
control_client_send(server, client, copy, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
}
}
client = client->next;
}
u_free(buffer);
}
/* ============================================================================
* Public API Implementation
* ============================================================================ */
+9 -3
View File
@@ -19,6 +19,8 @@ extern "C" {
/* Forward declarations */
struct UTUN_INSTANCE;
struct UASYNC;
struct NODEINFO_Q;
struct ll_queue;
/* Forward declaration */
struct control_server;
@@ -36,9 +38,10 @@ struct control_client {
/* Selected connection */
uint64_t selected_peer_id; /* 0 = none selected */
/* Client state */
// uint8_t connected;
struct ll_queue* output_queue; /* Output queue for async sends */
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
uint8_t write_registered; /* 1 = write events registered in uasync */
};
/* Control server state */
@@ -113,6 +116,9 @@ void control_server_process_updates(struct control_server* server);
*/
uint32_t control_server_get_client_count(const struct control_server* server);
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node);
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
#ifdef __cplusplus
}
#endif
+36 -4
View File
@@ -316,9 +316,6 @@ static void dummynet_delay_callback(void* user_arg) {
dir->stats.queue_max = queue_count + 1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)",
dir_idx, queue_count + 1);
/* Запускаем шейпер если не активен */
if (!dir->shaper_pending) {
dir->shaper_pending = 1;
@@ -358,7 +355,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
dn->listen_port = listen_port;
/* Создаём пул для пакетов */
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt));
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool");
if (!dn->pkt_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool");
u_free(dn);
@@ -580,6 +577,11 @@ struct dummynet_filter {
uint32_t jitter_ms;
struct ETCP_LINK* link;
struct dummynet_stats stats;
struct {
uint32_t ip;
uint16_t port;
} block_addrs[4];
uint8_t block_count;
};
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
@@ -588,6 +590,16 @@ static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t le
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
(void)link;
df->stats.recv++;
if (addr && addr->sa_family == AF_INET && df->block_count > 0) {
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
for (uint8_t i = 0; i < df->block_count; i++) {
if (sin->sin_addr.s_addr == df->block_addrs[i].ip &&
sin->sin_port == df->block_addrs[i].port) {
df->stats.lost++;
return (ssize_t)len;
}
}
}
if (df->loss_permille > 0) {
uint32_t roll = (uint32_t)rand() % 1000;
if (roll < df->loss_permille) {
@@ -640,6 +652,26 @@ void dummynet_filter_destroy(struct dummynet_filter* df) {
u_free(df);
}
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
if (!df || df->block_count >= 4) return;
for (uint8_t i = 0; i < df->block_count; i++)
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) return;
df->block_addrs[df->block_count].ip = ip;
df->block_addrs[df->block_count].port = port;
df->block_count++;
}
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
if (!df) return;
for (uint8_t i = 0; i < df->block_count; i++) {
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) {
df->block_count--;
if (i < df->block_count) memmove(&df->block_addrs[i], &df->block_addrs[i+1], (df->block_count - i) * sizeof(df->block_addrs[0]));
return;
}
}
}
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
return df ? &df->stats : NULL;
}
+2
View File
@@ -176,5 +176,7 @@ 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);
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
#endif // DUMMYNET_H
+138 -115
View File
@@ -16,6 +16,7 @@
#include <math.h> // For bandwidth calcs
#include <limits.h> // For UINT16_MAX
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
// Enable comprehensive debug output for ETCP module
#define DEBUG_CATEGORY_ETCP_DETAILED 1
@@ -198,13 +199,15 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->instance = instance;
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения
etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_send_q"); // Hash for send_q
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "recv_q"); // Hash for recv_q
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool");
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd;
etcp->optimal_inflight=100000;
etcp->initialized=0;
etcp->links_up=0;
@@ -300,23 +303,26 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
etcp->normalizer = NULL;
}
// Drain and free all queues using helper functions
// Clear links BEFORE draining queues: etcp_link_close calls
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
// which accesses input_send_q, input_queue, input_wait_ack.
if (etcp->links) {
struct ETCP_LINK* link = etcp->links;
while (link) {
struct ETCP_LINK* next = link->next;
etcp_link_close(link);
link = next;
}
etcp->links = NULL;
}
// Drain and free all queues
drain_and_free_queue(&etcp->input_queue);
drain_and_free_queue(&etcp->output_queue);
drain_and_free_queue(&etcp->input_send_q);
drain_and_free_queue(&etcp->input_wait_ack);
drain_and_free_queue(&etcp->recv_q);
drain_and_free_queue(&etcp->ack_q);
// Drain and free ack_q (contains ACK_PACKET from ack_pool - special handling)
if (etcp->ack_q) {
struct ACK_PACKET* pkt;
while ((pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q)) != NULL) {
queue_entry_free((struct ll_entry*)pkt);
}
queue_free(etcp->ack_q);
etcp->ack_q = NULL;
}
// Free memory pools after all elements are returned
if (etcp->inflight_pool) {
@@ -329,17 +335,6 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
etcp->io_pool = NULL;
}
// Clear links list safely
if (etcp->links) {
struct ETCP_LINK* link = etcp->links;
while (link) {
struct ETCP_LINK* next = link->next;
etcp_link_close(link);
link = next;
}
etcp->links = NULL;
}
u_free(etcp->name);
// Clear next pointer to prevent dangling references
@@ -389,17 +384,20 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
clear_queue(etcp->recv_q);
clear_queue(etcp->ack_q);
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
queue_resume_callback(etcp->input_queue);
queue_resume_callback(etcp->input_send_q);
queue_resume_callback(etcp->input_wait_ack);
// В etcp_conn_reset(), после очистки очередей добавьте:
struct ETCP_LINK* l = etcp->links;
while (l) {
l->window_pkt_transmitted = 0;
l->window_retransmissions = 0;
l->win_timebase = 50000;
l->win_ptr = 0;
memset(l->stat_win, 0, sizeof(l->stat_win));
start_stats_timer(l); // перезапускаем с дефолтным интервалом
l->inflight_bytes = 0;
l->inflight_packets = 0;
l->delivered_bytes = 0;
l->acked_bytes = 0;
l->acked_packets = 0;
l->last_ack_time_tb = get_time_tb();
l = l->next;
}
@@ -455,6 +453,11 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
}
}
clear_queue(etcp->input_send_q);
clear_queue(etcp->input_wait_ack);
queue_resume_callback(etcp->input_send_q);
queue_resume_callback(etcp->input_wait_ack);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "end");
}
@@ -614,6 +617,16 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
}
}
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
if (!etcp) return;
uint32_t sum = 0;
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
etcp->optimal_inflight = sum;
// input_queue_try_resume(etcp);
}
void etcp_stats(struct ETCP_CONN* etcp) {
if (!etcp) return;
@@ -754,8 +767,8 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
// shedule timer
int64_t next_timeout=timeout - elapsed;
if (next_timeout<0) next_timeout=0;
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
return;
}
current = etcp->input_wait_ack->head;
@@ -781,8 +794,8 @@ static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
etcp_conn_process_send_queue(etcp);
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) {
queue_resume_callback(etcp->input_send_q);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q");
queue_resume_callback(etcp->input_send_q);
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy");
}
/*
@@ -840,14 +853,12 @@ static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return;
etcp->tx_state = ETCP_TX_STATE_DATA_WAIT;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
queue_resume_callback(etcp->input_send_q);
// queue_resume_callback(etcp->ack_q);
if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) {
ack_response_timer_cb(etcp);
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
}
// Process packets in send queue and transmit them
@@ -858,7 +869,7 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
char l_status[256]={0};
struct ETCP_LINK* link = etcp->links;
while (link) {
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s,%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy", link->send_blocked_inflight?"inf_block":"rdy");
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy");
link = link->next;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send) %s", etcp->log_name, etcp->tx_state, l_status);
@@ -923,17 +934,21 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// Always subtract from previous last_link (if any) – this handles retransmission
if (inf_pkt->last_link) {
inf_pkt->last_link->window_retransmissions++;
inf_pkt->last_link->total_retransmissions++;
inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len;
bbr_note_loss(inf_pkt->last_link->bbr);
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
inf_pkt->last_link->inflight_packets--;
if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
}
// Always add to the CURRENT link (first send or retransmission)
link->inflight_bytes += inf_pkt->ll.len;
link->inflight_packets++;
link->window_pkt_transmitted++;
// BBR: сохраняем снэпшот на момент отправки
inf_pkt->delivered_at_send = link->delivered_bytes;
inf_pkt->inflight_at_send = link->inflight_bytes;
inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0;
// Update last_link for future ACK/retrans
inf_pkt->last_link = link;
@@ -1161,11 +1176,11 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move: ETCP -> PN");
// Add to output_queue using the same ETCP_FRAGMENT structure
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
next_expected_id, etcp->output_queue->count);
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
delivered_bytes += rx_pkt->ll.len;
delivered_count++;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moved packet id=%u to output_queue (qlen=%d)", etcp->log_name,
next_expected_id, etcp->output_queue->count);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
next_expected_id);
@@ -1212,11 +1227,50 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
return;
}
// === NEW: subtract inflight from the LAST link the packet was sent on ===
// === subtract inflight from the LAST link the packet was sent on ===
if (acked_pkt->last_link) {
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len;
acked_pkt->last_link->inflight_packets--;
if (acked_pkt->last_link->send_blocked_inflight && acked_pkt->last_link->inflight_bytes < acked_pkt->last_link->inflight_lim_bytes) loadbalancer_link_ready(acked_pkt->last_link);
struct ETCP_LINK* link = acked_pkt->last_link;
link->inflight_bytes -= acked_pkt->ll.len;
link->inflight_packets--;
// BBR: собираем rate_sample и вызываем bbr_main
uint64_t now_tb = get_time_tb();
uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0;
struct bbr_rate_sample rs = {
.delivered = acked_pkt->ll.len,
.interval_us = interval_us,
.rtt_us = (uint32_t)link->rtt_last * 100,
.acked_sacked = acked_pkt->ll.len,
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
.tx_in_flight = acked_pkt->inflight_at_send,
.lost = (int)link->bbr_loss_since_ack,
.is_app_limited = acked_pkt->is_app_limited,
};
link->delivered_bytes += rs.delivered;
link->last_ack_time_tb = now_tb;
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
rs.lost, rs.is_app_limited);
uint32_t old_cwnd = link->inflight_lim_bytes;
uint32_t old_pacing = link->bbr_pacing_rate;
uint32_t new_cwnd = old_cwnd;
uint32_t new_pacing = old_pacing;
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
(link->inflight_bytes >= link->inflight_lim_bytes));
etcp_link_update_inflight_lim(link, new_cwnd);
link->bbr_pacing_rate = new_pacing;
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
link->bbr_loss_since_ack = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u",
link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing,
link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd);
link->acked_bytes += acked_pkt->ll.len;
link->acked_packets++;
}
@@ -1247,6 +1301,10 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
if (!pkt) return;
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
if (!pkt->data_len) {
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
return;
@@ -1259,7 +1317,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t len = pkt->data_len;
uint16_t ts = pkt->timestamp; // Received timestamp
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt", etcp->log_name);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len);
while (len >= 1) {
uint8_t type = data[0];
@@ -1298,45 +1356,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t new_rtt=cur_ts-ret_ts;
pkt->link->rtt_last=new_rtt;
uint8_t old_idx = pkt->link->rtt_history_index;
uint16_t old_val = pkt->link->rtt_history[old_idx];
pkt->link->rtt_history[old_idx] = new_rtt;
pkt->link->rtt_history_index = (old_idx + 1) % 10;
if (pkt->link->rtt_history_count < 10) pkt->link->rtt_history_count++;
if (new_rtt >= pkt->link->rtt_max_val) {
pkt->link->rtt_max_val = new_rtt;
pkt->link->rtt_max_idx = old_idx;
} else if (old_val == pkt->link->rtt_max_val) {
pkt->link->rtt_max_idx = 255;
}
uint8_t rtt_cnt = pkt->link->rtt_history_count;
if (rtt_cnt == 1) {
pkt->link->rtt_avg10 = new_rtt;
} else {
uint32_t sum = 0;
if (pkt->link->rtt_max_idx == 255) {
uint16_t max_val = 0;
uint8_t max_pos = 0;
for (uint8_t i = 0; i < rtt_cnt; i++) {
uint16_t v = pkt->link->rtt_history[i];
sum += v;
if (v > max_val) {
max_val = v;
max_pos = i;
}
}
pkt->link->rtt_max_val = max_val;
pkt->link->rtt_max_idx = max_pos;
} else {
for (uint8_t i = 0; i < rtt_cnt; i++) {
sum += pkt->link->rtt_history[i];
}
}
pkt->link->rtt_avg10 = (sum - pkt->link->rtt_max_val) / (rtt_cnt - 1);
}
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts;
@@ -1347,29 +1366,30 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32;
pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536;
pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536;
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
data+=5; len-=5;
int d_rtt=pkt->link->rtt_avg10 - pkt->link->rtt_last;
int prev_rtt = pkt->link->rtt_last;
int d_rtt = new_rtt - prev_rtt;
if (d_rtt<0) d_rtt=-d_rtt;
pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32;
struct ETCP_LINK* c=etcp->links;
int rtt_sum=0;
int rtt_a10_max=0;
int rtt_max=0;
int tt_sum=0;
int j_sum=0;
int cnt=0;
while (c) {
rtt_sum += c->rtt_last;
if (rtt_a10_max < c->rtt_avg10) rtt_a10_max = c->rtt_avg10;
if (rtt_max < c->rtt_last) rtt_max = c->rtt_last;
tt_sum += c->tt_last;
j_sum += c->jitter/32768;
cnt++;
c=c->next;
}
etcp->rtt_last=rtt_sum/cnt;
etcp->rtt_avg_10=rtt_a10_max;
etcp->rtt_avg_10=rtt_max;
etcp->tt_last=tt_sum/cnt;
etcp->jitter=j_sum/cnt;
break;
@@ -1395,21 +1415,23 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
break;
}
}
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
len = 0;
break;
}
p->seq=seq;
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
if (queue_find_data_by_index(etcp->ack_q, &seq) == NULL) {
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
len = 0;
break;
}
p->seq=seq;
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
if (etcp->ack_resp_timer == NULL) {
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
if (etcp->ack_resp_timer == NULL) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
}
}
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
uint32_t pkt_len=len-5;
@@ -1450,8 +1472,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
}
// Copy the actual payload data
memcpy(payload_data, data + 5, pkt_len);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count);
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
} else {
etcp->rx_dup_count++;
@@ -1523,18 +1545,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min;
if (bw_kbps > 10000000) bw_kbps = 10000000;
link->bandwidth = (uint32_t)bw_kbps;
float rtt_sec = (float)link->rtt_min / 10000.f;
float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.f;
if (rtt_sec < 0.005f) rtt_sec = 0.005f;
link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec);
link->slow_start_threshold = link->burst_target_bdp * 9 / 10;
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
if (link->inflight_lim_bytes > link->slow_start_threshold) {
link->inflight_lim_bytes = link->slow_start_threshold;
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
}
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u, slow_start_thr=%u gap_min=%u us pkt_cnt=%u",
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, link->slow_start_threshold, gap_min, pkt_cnt);
// BBR: инициализируем верхнюю границу из burst
if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u",
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt);
}
}
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;
@@ -1557,6 +1574,12 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
}
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
}
+10 -2
View File
@@ -13,6 +13,7 @@ extern "C" {
#include "pkt_normalizer.h"
struct stcp_link; // forward declaration
// In struct ETCP_CONN, add:
//struct pn_pair* normalizer;
@@ -72,6 +73,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 (используем быстрый поиск с хешем)
@@ -101,6 +105,7 @@ struct ETCP_CONN {
// Links (channels) - linked list
struct ETCP_LINK* links;
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
struct stcp_link *transport_link; // non-NULL for TCP connections
// Crypto and state
struct secure_channel crypto_ctx;
@@ -128,7 +133,7 @@ struct ETCP_CONN {
// IDs and state
uint32_t next_tx_id; // Next TX ID
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
uint32_t last_rx_id; // Last received ID
uint32_t last_delivered_id; // Last delivered to output_queue
@@ -151,7 +156,7 @@ struct ETCP_CONN {
// Window and inflight management
uint32_t unacked_bytes; // Current inflight bytes
// uint32_t window_size; // Receive window - Not used
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
uint32_t optimal_inflight; // Sum over links
// Timers
@@ -227,6 +232,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);
+31 -163
View File
@@ -1,197 +1,65 @@
/**
* @file etcp_api.c
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
* @brief ETCP API — send/bind/recv dispatch
*/
#include "etcp_api.h"
#include "etcp.h"
#include "pkt_normalizer.h"
#include "utun_instance.h"
#include "stcp_link.h"
#include "../lib/debug_config.h"
#include <string.h>
#include "route_lib.h"
#include "route_bgp.h"
#define DEBUG_CATEGORY_ETCP_API 1
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
etcp->ready_cbk = callback_fn;
etcp->ready_arg = arg;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
if (e) { e->ready_cbk = fn; e->ready_arg = arg; }
}
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
etcp->up_cbk = callback_fn;
etcp->up_arg = arg;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
if (e) { e->up_cbk = fn; e->up_arg = arg; }
}
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
etcp->down_cbk = callback_fn;
etcp->down_arg = arg;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
if (e) { e->down_cbk = fn; e->down_arg = arg; }
}
// Установить callback при создании нового ETCP соединения
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
return;
}
instance->etcp_new_conn_cbk = callback_fn;
instance->etcp_new_conn_arg = arg;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: instance=%p cbk=%p arg=%p",
(void*)instance, (void*)callback_fn, arg);
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; }
}
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
if (!inst) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
return -1;
}
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: Overwriting existing callback for id=%u", id);
}
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
inst->api_bindings.callbacks[id] = callback;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
return 0;
}
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
if (!inst) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
return -1;
}
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
return -1;
}
if (inst->api_bindings.callbacks[id] == NULL) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
return -1;
}
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
inst->api_bindings.callbacks[id] = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
return 0;
}
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 || !pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
return -1;
}
int result = queue_data_put(pn->input, entry);
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
if (result != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
return 0;
if (!conn || !entry) return -1;
// TCP (stcp_link) — send directly
if (conn->transport_link)
return stcp_link_send((struct stcp_link *)conn->transport_link, entry->dgram, entry->len);
// UDP — через normalizer
if (!conn->normalizer || !conn->normalizer->input) return -1;
return queue_data_put(conn->normalizer->input, entry);
}
void etcp_int_recv(struct ll_queue* queue, void* arg) {
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
if (!queue || !conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
(void*)queue, (void*)conn);
return;
}
// Получаем элемент из очереди
struct ll_entry* entry = queue_data_get(queue);
if (!entry) {
// Очередь пуста - это нормально, просто возобновляем коллбэк
queue_resume_callback(queue);
return;
}
// Проверяем что есть данные
if (!entry->dgram || entry->len == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
queue_entry_free(entry);
queue_dgram_free(entry);
queue_resume_callback(queue);
return;
}
// Получаем ID пакета (первый байт кодограммы)
uint8_t id = entry->dgram[0];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
id, conn->log_name, (void*)conn);
// Получаем instance из соединения
if (!queue || !conn) return;
struct ll_entry* e = queue_data_get(queue);
if (!e) { queue_resume_callback(queue); return; }
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
uint8_t id = e->dgram[0];
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
queue_entry_free(entry);
queue_dgram_free(entry);
queue_resume_callback(queue);
return;
}
// Ищем binding для этого ID в instance
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]);
inst->api_bindings.callbacks[id](conn, entry);
} else {
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
if (inst->api_bindings.callbacks[0] != NULL) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
inst->api_bindings.callbacks[0](conn, entry);
} else {
// Нет default binding - освобождаем пакет
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
queue_entry_free(entry);
queue_dgram_free(entry);
}
}
// Возобновляем коллбэк для получения следующего пакета
if (!inst) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
if (inst->api_bindings.callbacks[id])
inst->api_bindings.callbacks[id](conn, e);
else if (inst->api_bindings.callbacks[0])
inst->api_bindings.callbacks[0](conn, e);
else { queue_entry_free(e); queue_dgram_free(e); }
queue_resume_callback(queue);
}
+6 -1
View File
@@ -6,6 +6,8 @@
* - etcp_send() - отправить пакет в очередь normalizer
* - etcp_bind() - подписаться на пакеты с определенным ID
* - etcp_int_recv() - коллбэк для сбора пакетов из всех подключений
* !!! для обычной отправки-приёма между узлами используем более универсальный модушь etcp_router.
* это в первую очередь - апи для использования etcp-router-ом
*
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
* cmd = 0 - пакет для передачи адресату
@@ -25,9 +27,12 @@
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy через удаленный узел (remote_proxy)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
#define ETCP_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
// Forward declarations
struct ETCP_CONN;
+938
View File
@@ -0,0 +1,938 @@
#include "etcp_bbr.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.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) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
bbr->link_id, bbr->mode, BBR_PROBE_BW);
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
bbr_start_bw_probe_cruise(bbr);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
bbr->link_id, bbr->mode, BBR_STARTUP);
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) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
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)) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
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) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
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)
{
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
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;
bbr->on_cwnd_update = NULL;
bbr->cwnd_update_ctx = NULL;
}
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);
if (bbr->on_cwnd_update) bbr->on_cwnd_update(bbr->cwnd_update_ctx, cwnd);
*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;
}
+112
View File
@@ -0,0 +1,112 @@
#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;
};
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
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;
uint8_t link_id; // local_link_id for debug logging
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
bbr_cwnd_update_fn on_cwnd_update;
void* cwnd_update_ctx;
};
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);
+505 -589
View File
File diff suppressed because it is too large. Load diff
+61 -38
View File
@@ -5,8 +5,8 @@
#include "secure_channel.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdint.h>
#include <stddef.h>
@@ -14,6 +14,9 @@
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define INFLIGHT_LIM_MIN 8192 // 8K
#define INFLIGHT_LIM_MAX 1048576 // 1M
#define PACKET_DATA_SIZE 1600//1536
#define PACKET_DATA_MAX_MTU 1600
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
@@ -26,6 +29,12 @@
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#define ETCP_KEEPALIVE 0x08
/* Адаптивный keepalive */
#define KA_PERIOD_MIN_MS 200
#define KA_PERIOD_MAX_MS 10000
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
#pragma pack(push, 1)
@@ -45,6 +54,45 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
// INIT REQUEST: client → server
struct ETCP_INIT_REQUEST_PKT {
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
uint8_t node_id[8]; // 1: sender node_id (big-endian)
uint8_t session_id[4]; // 9: session (big-endian)
uint8_t mtu[2]; // 13: client MTU (big-endian)
uint8_t keepalive[2]; // 15: keepalive interval (big-endian)
uint8_t recovery[2]; // 17: recovery interval/100 (big-endian)
uint8_t link_id; // 19: client's local link id
uint8_t socket_id; // 20: client's socket id
uint8_t only_local; // 21: client only_local flag
uint8_t type; // 22: client socket type (CFG_SERVER_TYPE_*)
// V2 fields (NAT_DIRECT detection):
uint8_t src_ipv4[4]; // 23: client interface_addr IPv4 (big-endian, 0 if N/A)
uint8_t src_port[2]; // 27: client interface_addr port (big-endian)
} __attribute__((packed));
#define ETCP_INIT_REQ_V1_SIZE 23 // size without src_ipv4/src_port (backward compat)
#define ETCP_INIT_REQ_V2_SIZE 29 // size with src_ipv4/src_port
// INIT RESPONSE: server → client
struct ETCP_INIT_RESPONSE_PKT {
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
uint8_t node_id[8]; // 1: server node_id (big-endian)
uint8_t session_id[4]; // 9: session (big-endian)
uint8_t mtu[2]; // 13: server MTU (big-endian)
uint8_t link_id; // 15: server's local link id
uint8_t remote_socket_id; // 16: echo of client's socket_id
uint8_t only_local; // 17: server only_local flag
uint8_t type; // 18: server socket type (CFG_SERVER_TYPE_*)
// V2 fields:
uint8_t peer_ipv4[4]; // 19: client's external NAT address (big-endian)
uint8_t peer_port[2]; // 23: client's external NAT port (big-endian)
} __attribute__((packed));
#define ETCP_INIT_RESP_V1_SIZE 19 // size without peer_ipv4/peer_port
#define ETCP_INIT_RESP_V2_SIZE 25 // size with peer_ipv4/peer_port
#define ETCP_INIT_RESP_MIN_SIZE ETCP_INIT_RESP_V1_SIZE
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 {
@@ -97,6 +145,7 @@ struct ETCP_SOCKET {
#define NAT_TYPE_UNKNOWN 0
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
// Verified NAT types (published in nodeinfo after server-side NAT detection)
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
@@ -121,7 +170,6 @@ struct ETCP_LINK {
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
SlidingWindowMin* rtt_swm;
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
@@ -163,26 +211,6 @@ struct ETCP_LINK {
uint32_t inflight_packets;
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;
size_t decrypt_errors;
@@ -191,16 +219,12 @@ struct ETCP_LINK {
size_t total_encrypted;
size_t total_decrypted;
uint32_t total_retransmissions;
uint64_t acked_bytes; // подтверждённых байт на этом линке
uint64_t acked_packets; // подтверждённых пакетов на этом линке
uint16_t rtt_last; // round trip (время отправки + приёма)
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
uint8_t rtt_history_index; // Current write position in history buffer
uint32_t jitter; // Current jitter [>>16] x0.1 ms
uint16_t rtt_max_val; // Current max RTT in history
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
uint16_t rtt_avg10; // round trip average (excl. max)
uint16_t rtt_min; // round trip min (rtt_swm floating window)
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
@@ -218,6 +242,7 @@ struct ETCP_LINK {
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
uint32_t keepalive_timeout; // таймаут (ms)
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
@@ -249,11 +274,12 @@ struct ETCP_LINK {
uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения
// === Фазы управления inflight ===
// 0 = slow_start, 1 = congestion_avoidance
uint8_t inflight_phase;
uint64_t last_window_update_tb; // время последнего обновления окна (0.1ms)
uint32_t slow_start_threshold; // порог выхода из slow start (burst_target_bdp * 0.9)
// === BBR congestion control ===
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx;
@@ -278,20 +304,17 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
// connection functions
// создает новый канал связи для etcp подключения (ETCP_CONN)
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
// find free local_link_id for connection
// scans all links in connection, marks used ids in bit array
// returns first free id (0-255) or -1 if all occupied
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
void start_stats_timer(struct ETCP_LINK* link);
// Burst measurement functions
void etcp_link_burst_start(struct ETCP_LINK* link);
+14 -23
View File
@@ -4,7 +4,6 @@
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdio.h>
#include <string.h>
@@ -153,32 +152,29 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->keepalive_interval, link->keepalive_timeout);
/* timers */
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s",
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s burst=%s keepalive=%s",
link->init_timer ? "ACTIVE" : "free",
link->init_timeout, link->init_retry_count,
link->keepalive_timer ? "ACTIVE" : "free",
link->shaper_timer ? "ACTIVE" : "free",
link->stats_timer ? "ACTIVE" : "free",
link->burst_resp_timer ? "ACTIVE" : "free",
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* inflight */
DUMP_LINE("INFLIGHT: bytes=%u pkts=%u lim=%u blocked=%d phase=%d sst=%u last_win_tb=%llu",
/* inflight (BBR) */
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u mode=%d cycle=%d pacing=%u",
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
link->send_blocked_inflight, link->inflight_phase,
link->slow_start_threshold, (unsigned long long)link->last_window_update_tb);
link->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
link->bbr_pacing_rate);
/* rtt */
char hist_str[128] = "";
int hist_pos = 0;
for (int i = 0; i < 10 && i < (int)link->rtt_history_count; i++) {
hist_pos += snprintf(hist_str + hist_pos, sizeof(hist_str) - hist_pos,
"%s%u", i == 0 ? "" : ",", link->rtt_history[i]);
}
DUMP_LINE("RTT: last=%u avg10=%u min=%u max=%u jitter=%u hist=[%s] cnt=%d swm=%u",
link->rtt_last, link->rtt_avg10, link->rtt_min, link->rtt_max_val,
link->jitter, hist_str, link->rtt_history_count,
link->rtt_swm ? swm_get_min(link->rtt_swm) : 0);
/* rtt (BBR) */
DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
link->rtt_last, link->jitter,
link->bbr ? link->bbr->min_rtt_us : 0,
link->bbr ? link->bbr->bw_lo : 0,
link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
link->bbr ? link->bbr->inflight_lo : 0,
link->bbr ? link->bbr->inflight_hi : 0);
/* tt/rt/bandwidth */
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
@@ -201,11 +197,6 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->nat_changes_count, link->nat_hits_count,
link->nat_check_status, link->nat_type);
/* stats win */
DUMP_LINE("WIN: ptr=%u tb=%u tx=%u retrans=%u",
link->win_ptr, link->win_timebase,
link->window_pkt_transmitted, link->window_retransmissions);
/* last recv */
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
(unsigned long long)link->last_recv_local_time,
+7 -19
View File
@@ -57,8 +57,8 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
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");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: tmr:%s",
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
return NULL;
}
@@ -164,9 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
uint64_t now_tb=get_time_tb();
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduling shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb, "shaper");
if (link->shaper_timer == NULL) DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Filed to allocate timer");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
}
// Inactivity correction
@@ -181,20 +181,8 @@ 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;
can = 0;
} else {
link->send_blocked_inflight = 0;
}
if (link->shaper_timer) {
// link->send_blocked_bandwidth = 1;
can = 0;
} else {
// link->send_blocked_bandwidth = 0;
}
return can;
if (link->shaper_timer) return 0;
return 1;
}
void loadbalancer_link_ready(struct ETCP_LINK* link) {
@@ -202,8 +190,8 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
return;
}
if (loadbalancer_link_can_send(link) == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
if (link->shaper_timer) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
+338 -95
View File
@@ -22,7 +22,7 @@ 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 int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed);
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);
@@ -47,15 +47,16 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
// ====================================================================
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);
return router_send_one_flags(rconn, payload, pl_len, 0, 0);
}
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed) {
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;
size_t sig_len = is_signed ? SC_SIGN_SIZE : 0;
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
uint8_t* dgram = u_malloc(total_len);
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
@@ -64,23 +65,64 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
hdr->src_node_id = inst->node_id;
hdr->seq = seq;
hdr->svc_id = rconn->svc_id;
{
uint8_t f = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
if (!rconn->start_sent && pl_len > 0 && !(seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
f |= ROUTER_FLAG_START;
rconn->start_sent = 1;
}
if (is_signed) f |= ROUTER_FLAG_SIGNED;
hdr->flags = f;
}
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, payload, pl_len);
struct ETCP_CONN* conn = NULL;
struct ROUTE_BGP* bgp = inst->bgp;
if (bgp) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, rconn->remote_node_id);
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
conn = route_bgp_find_conn_for_node(bgp, nq->conn_mgr_intermediaries[i]);
if (conn) break;
}
}
}
if (!conn) conn = route_bgp_find_conn_for_node(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);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
if (is_signed) {
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&conn->crypto_ctx, &sign_state) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_init failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
size_t data_len = SVC_ROUTE_HDR_SIZE + pl_len;
if (sc_stream_sign_update(&sign_state, dgram, data_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_update failed svc_id=%u", rconn->svc_id);
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
size_t actual_sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + data_len, &actual_sig_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_final failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
}
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
entry->dgram = dgram;
entry->len = total_len;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_send_one: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(entry); queue_dgram_free(entry); if (!seq_flags) rconn->tx_seq--; return -1;
}
rconn->last_dgram_ts = get_current_timestamp();
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "router_send: seq=%u → %016llx svc_id=%u len=%zu inflight=%u",
seq, (unsigned long long)rconn->remote_node_id, rconn->svc_id, pl_len,
rconn->tx_seq - rconn->rx_acked);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d signed=%d → %016llx",
rconn->svc_id, seq, pl_len,
(int32_t)(rconn->tx_seq - rconn->tx_acked),
is_signed,
(unsigned long long)rconn->remote_node_id);
return etcp_send(conn, entry);
}
@@ -99,7 +141,7 @@ static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
entry->len = SVC_ROUTE_HDR_SIZE;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
seq_flags, (unsigned long long)dst, svc_id);
etcp_send(conn, entry);
}
@@ -110,11 +152,12 @@ static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
if (!cb) return;
struct ll_entry* e = queue_entry_new(0);
if (!e) return;
e->dgram = u_malloc(1);
e->dgram = u_malloc(9);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = rconn->svc_id;
e->len = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_close_svc: svc_id=%u remote=%016llx",
memcpy(e->dgram + 1, &rconn->remote_node_id, 8);
e->len = 9;
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_close_svc: svc_id=%u remote=%016llx",
rconn->svc_id, (unsigned long long)rconn->remote_node_id);
cb(NULL, e);
}
@@ -126,7 +169,7 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
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 (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG, 0);
// Таймеры
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; }
@@ -143,14 +186,17 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
}
if (inst->router_conns)
queue_remove_data(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: closed remote=%016llx svc_id=%u",
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
}
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
int sq = queue_entry_count(rconn->send_q);
if (sq > 0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_DRAIN: svc_id=%u send_q=%d inflight=%d",
rconn->svc_id, sq, (int32_t)(rconn->tx_seq - rconn->tx_acked));
while (1) {
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) break;
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
struct ll_entry* e = queue_data_get(rconn->send_q);
if (!e) { rconn->send_blocked = 0; break; }
router_send_one(rconn, e->dgram, e->len);
@@ -171,84 +217,102 @@ static void router_send_resume_cb(void* arg) {
router_drain_send_q(rconn);
}
static void router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, int force) {
if (!force && queue_entry_count(rconn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: FULL svc_id=%u count=%d — backpressure",
rconn->svc_id, queue_entry_count(rconn->send_q));
return -1;
}
struct ll_entry* qe = queue_entry_new(0);
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_enqueue_send: queue_entry_new failed"); return; }
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_enqueue_send: queue_entry_new failed"); return -1; }
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_entry_free(qe); return; }
if (!qe->dgram) { queue_entry_free(qe); return -1; }
qe->len = pl_len;
if (pl_len > 0) memcpy(qe->dgram, payload, pl_len);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: queued svc_id=%u inflight=%d send_q=%d",
rconn->svc_id, (int32_t)(rconn->tx_seq - rconn->tx_acked), queue_entry_count(rconn->send_q));
queue_data_put(rconn->send_q, qe);
rconn->send_blocked = 1;
if (!rconn->send_resume_timer)
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_send_q: queued svc_id=%u inflight=%u send_q=%d",
rconn->svc_id, rconn->tx_seq - rconn->rx_acked, queue_entry_count(rconn->send_q));
return 0;
}
// ====================================================================
// ACK timers
// ====================================================================
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
if (rconn->idle_ack_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->idle_ack_timer);
rconn->idle_ack_timer = NULL;
}
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
static void router_ack_do_send(struct ETCP_ROUTER_CONN* rconn) {
router_send_ack(rconn);
rconn->last_sent_ack_seq = rconn->rx_seq;
rconn->last_ack_sent_tb = get_time_tb();
}
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
struct UTUN_INSTANCE* inst = rconn->inst;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_ack: no route to %016llx svc_id=%u",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_ack: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
return;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_ack: u_malloc failed"); return; }
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_ack: u_malloc failed"); return; }
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = rconn->rx_seq;
hdr->svc_id = rconn->svc_id;
hdr->flags = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
entry->len = SVC_ROUTE_HDR_SIZE;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router_ack: → %016llx svc_id=%u rx_seq=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id, rconn->rx_seq);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ACK_SEND: svc_id=%u rx_seq=%u → %016llx",
rconn->svc_id, rconn->rx_seq, (unsigned long long)rconn->remote_node_id);
etcp_send(conn, entry);
}
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
if (rconn->idle_ack_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->idle_ack_timer);
rconn->idle_ack_timer = NULL;
}
if (rconn->rx_seq == rconn->last_sent_ack_seq) return;
uint64_t now = get_time_tb();
uint64_t elapsed = now - rconn->last_ack_sent_tb;
if (elapsed >= ROUTER_ACK_INTERVAL_TB) {
router_ack_do_send(rconn);
return;
}
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB - (uint32_t)elapsed, rconn, router_ack_timer_cb, "router_ack");
}
static void router_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->ack_timer = NULL;
if (rconn->rx_seq != rconn->rx_acked) {
router_send_ack(rconn);
rconn->rx_acked = rconn->rx_seq;
}
if (rconn->rx_seq != rconn->rx_acked)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
else
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
if (rconn->rx_seq != rconn->last_sent_ack_seq) {
if (!rconn->ack_timer)
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
} else if (!rconn->idle_ack_timer) {
rconn->idle_ack_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_ACK_IDLE_TB, rconn, router_idle_ack_timer_cb, "router_idle_ack");
}
}
static void router_idle_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
rconn->idle_ack_timer = NULL;
if (rconn->rx_seq != rconn->rx_acked) {
router_send_ack(rconn);
rconn->rx_acked = rconn->rx_seq;
}
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
}
// ====================================================================
@@ -260,19 +324,19 @@ static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* con
struct UTUN_INSTANCE* inst = rconn->inst;
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
if (!cb) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "router_deliver: no handler for svc_id=%u", rconn->svc_id);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: no handler for svc_id=%u", rconn->svc_id);
return;
}
size_t entry_len = 1 + payload_len;
struct ll_entry* svc_entry = queue_entry_new(0);
if (!svc_entry) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_deliver: queue_entry_new failed"); return; }
if (!svc_entry) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: queue_entry_new failed"); return; }
svc_entry->len = entry_len;
svc_entry->dgram = u_malloc(entry_len);
if (!svc_entry->dgram) { queue_entry_free(svc_entry); return; }
svc_entry->dgram[0] = rconn->svc_id;
if (payload_len > 0) memcpy(svc_entry->dgram + 1, payload, payload_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
cb(conn, svc_entry);
}
@@ -299,7 +363,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
if (!entry) return;
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
if (!inst || entry->len < SVC_ROUTE_HDR_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router: invalid packet inst=%p len=%zu min=%u",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: invalid packet inst=%p len=%zu min=%u",
(void*)inst, entry->len, (unsigned)SVC_ROUTE_HDR_SIZE);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
@@ -308,7 +372,56 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_RECV: svc_id=%u seq=%u len=%zu from %016llx",
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
if (hdr->dst_node_id == inst->node_id) {
// ========== Проверка подписи (если флаг ROUTER_FLAG_SIGNED) ==========
size_t actual_payload = pl_len;
if (hdr->flags & ROUTER_FLAG_SIGNED) {
if (pl_len < SC_SIGN_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet too short len=%zu from %016llx", pl_len, (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
actual_payload = pl_len - SC_SIGN_SIZE;
uint8_t* sig = pl + actual_payload;
struct ROUTE_BGP* bgp = inst->bgp;
struct NODEINFO_Q* nq = bgp ? nodeinfo_find_by_id(bgp, hdr->src_node_id) : NULL;
if (!nq) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet from unknown node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
{
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(nq->node.ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no Ed25519 pubkey for node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
struct sc_stream_sign_state vfy_state;
if (sc_stream_sign_verify_init(&vfy_state, nq->node.ed25519_public_key) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_verify_init failed for node %016llx", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
size_t signed_len = SVC_ROUTE_HDR_SIZE + actual_payload;
if (sc_stream_sign_update(&vfy_state, entry->dgram, signed_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_update failed for node %016llx", (unsigned long long)hdr->src_node_id);
sc_stream_sign_cleanup(&vfy_state); queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (sc_stream_sign_verify(&vfy_state, sig, SC_SIGN_SIZE) != SC_OK) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: signature verification FAILED for node %016llx svc_id=%u — dropping", (unsigned long long)hdr->src_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
pl_len = actual_payload;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: signature verified for node %016llx svc_id=%u", (unsigned long long)hdr->src_node_id, hdr->svc_id);
}
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
@@ -320,9 +433,17 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
}
if (pl_len == 0) {
// ACK-пакет: обновляем rx_acked, drain send_q, выходим
if (rconn) {
rconn->rx_acked = hdr->seq;
if ((int32_t)(hdr->seq - rconn->tx_acked) >= 0) {
rconn->tx_acked = hdr->seq;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d from %016llx",
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
queue_entry_count(rconn->send_q),
(unsigned long long)hdr->src_node_id);
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
hdr->seq, rconn->tx_acked, (unsigned long long)hdr->src_node_id);
}
rconn->last_dgram_ts = get_current_timestamp();
if (rconn->send_blocked) router_drain_send_q(rconn);
}
@@ -336,6 +457,31 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
}
// Обнаружение перезапуска peer'а: новый sess_id или START-флаг
{
uint8_t incoming_sess = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
if (pl_len > 0 && (incoming_sess != rconn->peer_sess_id ||
((hdr->flags & ROUTER_FLAG_START) && rconn->rx_seq > 0))) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: peer restart sess_id=%u→%u svc_id=%u from %016llx",
rconn->peer_sess_id, incoming_sess, hdr->svc_id,
(unsigned long long)hdr->src_node_id);
etcp_router_conn_restart(inst, hdr->src_node_id, hdr->svc_id);
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
rconn->peer_sess_id = incoming_sess;
}
// RST-флаг — peer сигналит нарушение последовательности
if (hdr->flags & ROUTER_FLAG_RST) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: RST from %016llx svc_id=%u",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
// Seq-состояние есть — reorder
rconn->last_dgram_ts = get_current_timestamp();
uint32_t seq = hdr->seq;
@@ -344,7 +490,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
{
int32_t d = (int32_t)(seq - rconn->rx_seq);
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
seq, rconn->rx_seq, d);
queue_dgram_free(entry); queue_entry_free(entry);
return;
@@ -354,7 +500,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// Дубликат (seq уже доставлен или в recv_q)
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
queue_find_data_by_index(rconn->recv_q, &seq)) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
@@ -368,10 +514,10 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
qe->len = pl_len;
memcpy(qe->dgram, pl, pl_len);
queue_data_put_with_index(rconn->recv_q, qe);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
(unsigned long long)hdr->src_node_id, rconn->svc_id);
queue_data_put_with_index(rconn->recv_q, qe);
if (seq == rconn->rx_seq)
router_try_assembly(rconn, conn);
@@ -382,12 +528,12 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// ========== Транзит ==========
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
if (!next) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router: no route to %016llx svc_id=%u, dropping",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_router: forwarding svc_id=%u → %016llx via %s",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
etcp_send(next, entry);
}
@@ -398,21 +544,21 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
// ====================================================================
int etcp_router_init(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: NULL instance"); return -1; }
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: NULL instance"); return -1; }
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
inst->router_conns = queue_new(inst->ua,
ROUTER_CONN_HASH_SIZE, 0, 9, "router_conns");
if (!inst->router_conns) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: queue_new(router_conns) failed");
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: queue_new(router_conns) failed");
return -1;
}
int ret = etcp_bind(inst, ETCP_ID_SVC_ROUTE, etcp_router_recv_cb);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_init: etcp_bind failed, ret=%d", ret);
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: etcp_bind failed, ret=%d", ret);
queue_free(inst->router_conns); inst->router_conns = NULL;
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
return 0;
}
@@ -424,9 +570,9 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
router_send_close_to_service(rconn);
if (rconn->ack_timer) uasync_cancel_timeout(inst->ua, rconn->ack_timer);
if (rconn->idle_ack_timer) uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer);
if (rconn->send_resume_timer) uasync_cancel_timeout(inst->ua, rconn->send_resume_timer);
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
@@ -443,43 +589,43 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
inst->router_conns = NULL;
}
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router destroyed");
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router destroyed");
}
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_bind: NULL instance"); return -1; }
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_bind: NULL callback for svc_id=%u", svc_id); return -1; }
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: NULL instance"); return -1; }
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: NULL callback for svc_id=%u", svc_id); return -1; }
if (inst->router_bindings.callbacks[svc_id])
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router_bind: overwriting svc_id=%u", svc_id);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: overwriting svc_id=%u", svc_id);
inst->router_bindings.callbacks[svc_id] = callback;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router_bind: svc_id=%u → cb=%p", svc_id, (void*)callback);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: svc_id=%u → cb=%p", svc_id, (void*)callback);
return 0;
}
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: NULL instance"); return -1; }
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: NULL instance"); return -1; }
if (!inst->router_bindings.callbacks[svc_id]) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: svc_id=%u not bound", svc_id);
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: svc_id=%u not bound", svc_id);
return -1;
}
inst->router_bindings.callbacks[svc_id] = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_router_unbind: svc_id=%u", svc_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: svc_id=%u", svc_id);
return 0;
}
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry) {
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force) {
if (!inst || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_route_send: NULL inst=%p entry=%p", (void*)inst, (void*)entry);
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: NULL inst=%p entry=%p", (void*)inst, (void*)entry);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1;
}
if (!entry->dgram || entry->len < 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_route_send: empty entry"); queue_dgram_free(entry); queue_entry_free(entry); return -1;
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: empty entry"); queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
uint8_t svc_id = entry->dgram[0];
size_t payload_len = entry->len - 1;
if (dst_node_id == inst->node_id) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_route_send: loopback svc_id=%u len=%zu", svc_id, payload_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: loopback svc_id=%u len=%zu", svc_id, payload_len);
if (inst->router_bindings.callbacks[svc_id])
inst->router_bindings.callbacks[svc_id](NULL, entry);
else { queue_dgram_free(entry); queue_entry_free(entry); }
@@ -488,7 +634,7 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
if (!etcp_conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
(unsigned long long)dst_node_id, svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return -1;
@@ -497,10 +643,10 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, dst_node_id, svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) {
router_enqueue_send(rconn, entry->dgram + 1, payload_len);
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
int eq_ret = router_enqueue_send(rconn, entry->dgram + 1, payload_len, force);
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
return eq_ret;
}
int ret = router_send_one(rconn, entry->dgram + 1, payload_len);
@@ -515,6 +661,84 @@ int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
return queue_entry_count(conn->normalizer->input);
}
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_wait(conn->normalizer->input, h, callback, arg);
}
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_cancel(conn->normalizer->input, h);
}
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id) {
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
if (!rconn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_restart: svc_id=%u remote=%016llx — resetting local state",
svc_id, (unsigned long long)remote_node_id);
etcp_recv_fn cb = inst->router_bindings.callbacks[svc_id];
if (cb) {
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(9);
if (e->dgram) {
e->dgram[0] = svc_id;
memcpy(e->dgram + 1, &remote_node_id, 8);
e->len = 9;
cb(NULL, e);
} else { queue_entry_free(e); }
}
}
struct ll_entry* f;
if (rconn->send_q) {
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
if (rconn->recv_q) {
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
rconn->tx_seq = 0;
rconn->rx_seq = 0;
rconn->tx_acked = 0;
rconn->last_sent_ack_seq = 0;
rconn->sess_id = (rconn->sess_id + 1) & 0x3;
rconn->peer_sess_id = 0;
rconn->start_sent = 0;
rconn->send_blocked = 0;
}
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!inst || !h) return;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
queue_waiter_wait(rconn->send_q, h, callback, arg);
}
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h) {
if (!inst || !h) return;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
queue_waiter_cancel(rconn->send_q, h);
}
// ====================================================================
// Seq-connection API
// ====================================================================
@@ -527,42 +751,61 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
size_t data_size = sizeof(struct ETCP_ROUTER_CONN) - sizeof(struct ll_entry);
rconn = (struct ETCP_ROUTER_CONN*)queue_entry_new(data_size);
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_entry_new failed"); return NULL; }
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_entry_new failed"); return NULL; }
rconn->remote_node_id = remote_node_id;
rconn->svc_id = svc_id;
rconn->last_dgram_ts = get_current_timestamp();
rconn->tx_seq = 0;
rconn->rx_seq = 0;
rconn->rx_acked = 0;
rconn->tx_acked = 0;
rconn->last_sent_ack_seq = 0;
rconn->last_ack_sent_tb = 0;
rconn->inst = inst;
rconn->ack_timer = NULL;
rconn->idle_ack_timer = NULL;
rconn->send_blocked = 0;
rconn->send_resume_timer = NULL;
rconn->sess_id = 0;
rconn->peer_sess_id = 0;
rconn->start_sent = 0;
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_data_put_with_index(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: new conn remote=%016llx svc_id=%u",
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: new conn remote=%016llx svc_id=%u",
(unsigned long long)remote_node_id, svc_id);
queue_data_put_with_index(inst->router_conns, &rconn->ll);
return rconn;
}
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len) {
if (!rconn || !data || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "router_conn_send: invalid args rconn=%p data=%p len=%zu",
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send: invalid args rconn=%p data=%p len=%zu",
(void*)rconn, (const void*)data, len);
return -1;
}
if (rconn->tx_seq - rconn->rx_acked >= ROUTER_MAX_INFLIGHT) {
router_enqueue_send(rconn, data, len);
return 0;
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
return router_enqueue_send(rconn, data, len, 0);
}
return router_send_one(rconn, data, len);
}
int etcp_router_conn_send_signed(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_signed: 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) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: inflight full svc_id=%u", rconn->svc_id);
return -1;
}
return router_send_one_flags(rconn, data, len, 0, 1);
}
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
router_close_and_notify(rconn);
}
@@ -587,7 +830,7 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t re
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "router_conn: closed by node remote=%016llx svc_id=%u",
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed by node remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
} else {
+44 -6
View File
@@ -3,7 +3,8 @@
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [payload...]
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [payload...]
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3)
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
@@ -18,11 +19,19 @@ struct SVC_ROUTE_HDR {
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
uint8_t svc_id; // идентификатор сервиса
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
};
#pragma pack(pop)
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 22
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
#define SVC_ROUTE_MAX_BINDINGS 256
// Биты в flags
#define ROUTER_FLAG_START 0x80
#define ROUTER_FLAG_RST 0x40
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
#define ROUTER_SESS_ID_SHIFT 4
#define ROUTER_SESS_ID_MASK 0x30
// Состояние одного логического подключения (remote_node_id + svc_id)
struct ETCP_ROUTER_CONN {
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
@@ -32,24 +41,30 @@ struct ETCP_ROUTER_CONN {
uint32_t tx_seq; // следующий seq для отправки
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
uint32_t rx_acked; // последний отправленный ACK (= rx_seq на момент отправки)
uint32_t tx_acked; // сколько наших пакетов подтвердил remote (для inflight)
uint32_t last_sent_ack_seq; // последний отправленный ACK (= rx_seq на момент отправки)
uint64_t last_ack_sent_tb; // время отправки последнего ACK (timebase 0.1ms)
struct UTUN_INSTANCE* inst;
struct ll_queue* recv_q; // reorder очередь: hash по seq (4 байта, offset 0)
void* ack_timer; // периодический таймер (100ms)
void* ack_timer; // периодический таймер (10ms при consumer_ack)
void* idle_ack_timer; // idle таймер (дослать последний ack)
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
void* send_resume_timer; // таймер возобновления отправки
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
uint8_t sess_id; // наш session id (0-3)
uint8_t peer_sess_id; // последний sess_id от peer'а
uint8_t start_sent; // 0 = нужно отправить START в первом data
};
#define ROUTER_CONN_HASH_SIZE 256
#define ROUTER_RECVQ_HASH_SIZE 1024
#define ROUTER_MAX_INFLIGHT 256 // макс пакетов в полёте (для контроля inflight)
#define ROUTER_ACK_INTERVAL_TB 1000 // интервал ACK: 100ms в timebase (0.1ms)
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
// Флаги в старших битах seq (взаимоисключающие)
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
@@ -71,7 +86,7 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn c
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry);
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force);
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
@@ -82,6 +97,10 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
@@ -91,4 +110,23 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t re
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
// Backpressure: зарегистрировать/отменить waiter на normalizer->input очереди
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h);
// Backpressure: зарегистрировать/отменить waiter на send_q очереди
// h — handle из структуры сервиса, callback вызывается когда send_q.count <= threshold (128)
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h);
// Сбросить состояние роутера для конкретного peer+svc (перезапуск удалённой стороны).
// Очищает send_q/recv_q, уведомляет сервис через cb(NULL, entry) с remote_node_id.
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id);
#endif // ETCP_ROUTER_H
+39 -7
View File
@@ -95,15 +95,15 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
ctx->ua = ua;
ctx->output = output;
ctx->output_arg = output_arg;
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb));
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen));
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg));
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb), "pcb_pool");
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen), "pcb_listen_pool");
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg), "seg_pool");
if (!ctx->pcb_pool || !ctx->pcb_listen_pool || !ctx->seg_pool) {
lwip_tcp_destroy(ctx);
return NULL;
}
ctx->iss_seed = (uint16_t)(get_time_tb() & 0xFFFF);
ctx->tmr_interval_ms = TCP_TMR_INTERVAL;
ctx->tmr_interval_ms = TCP_TMR_INTERVAL / 4;
ctx->rto_min_ms = 3000;
ctx->rto_max_ms = 0;
ctx->timer = uasync_set_timeout(ua, ctx->tmr_interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr");
@@ -788,6 +788,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
prev = NULL;
pcb = ctx->tw_pcbs;
while (pcb != NULL) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_slowtmr — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_slowtmr — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
pcb_remove = 0;
if ((uint32_t)(ctx->ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
@@ -804,7 +812,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
}
pcb2 = pcb;
pcb = pcb->next;
int self_loop = (pcb == pcb2);
tcp_free(pcb2);
if (self_loop) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_slowtmr: freed pcb=%p state=TIME_WAIT port=%u, clearing tw_pcbs",
(void*)pcb2, pcb2->local_port);
ctx->tw_pcbs = NULL;
break;
}
} else {
prev = pcb;
pcb = pcb->next;
@@ -1017,12 +1032,29 @@ static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx)
inactivity = 0;
inactive = NULL;
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_kill_timewait — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_kill_timewait — HALTING", (void*)pcb);
volatile int _halt = 1; while (_halt) {}
}
if ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity) {
inactivity = ctx->ticks - pcb->tmr;
inactive = pcb;
}
}
if (inactive != NULL) tcp_abort(inactive);
if (inactive != NULL) {
if (inactive->next == inactive) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_kill_timewait: aborting pcb=%p, clearing tw_pcbs",
(void*)inactive);
ctx->tw_pcbs = NULL;
tcp_free(inactive);
} else {
tcp_abort(inactive);
}
}
}
static void tcp_handle_closepend(struct lwip_tcp_ctx *ctx)
@@ -1080,8 +1112,8 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->ttl = 64;
pcb->mss = INITIAL_MSS;
pcb->rto = 3000 / TCP_SLOW_INTERVAL;
pcb->sv = 3000 / TCP_SLOW_INTERVAL;
pcb->rto = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->sv = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->rtime = -1;
pcb->cwnd = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "tcp_alloc: cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
+2 -2
View File
@@ -166,7 +166,6 @@ struct tcp_pcb {
tcp_connected_fn connected;
tcp_poll_fn poll;
tcp_err_fn errf;
uint32_t keep_idle;
uint8_t persist_cnt;
uint8_t persist_backoff;
@@ -185,7 +184,7 @@ struct tcp_pcb_listen {
tcp_accept_fn accept;
};
// Context for the TCP module (one per tcp_proxy instance)
// Context for the TCP module (one per tcp_proxy_client instance)
struct lwip_tcp_ctx {
struct UASYNC *ua;
tcp_output_fn output;
@@ -247,6 +246,7 @@ err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ip, uint16_t port, tcp_connected
err_t tcp_write(struct tcp_pcb *pcb, const void *data, uint16_t len, uint8_t apiflags);
err_t tcp_output(struct tcp_pcb *pcb);
err_t tcp_close(struct tcp_pcb *pcb);
err_t tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx);
void tcp_abort(struct tcp_pcb *pcb);
void tcp_recved(struct tcp_pcb *pcb, uint16_t len);
+17 -1
View File
@@ -208,11 +208,27 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
if (pcb == NULL) {
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
if (!pcb->ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in lwip_tcp_input — HALTING", (void*)pcb);
pbuf_free(p);
volatile int _halt = 1; while (_halt) {}
}
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in lwip_tcp_input — HALTING", (void*)pcb);
pbuf_free(p);
volatile int _halt = 1; while (_halt) {}
}
if (pcb->remote_port == sport &&
pcb->local_port == dport &&
pcb->remote_ip == src_ip &&
pcb->local_ip == dst_ip) {
tcp_timewait_input(pcb);
if (pcb->next == pcb) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in lwip_tcp_input: pcb=%p sport=%u dport=%u, aborting",
(void*)pcb, sport, dport);
tcp_abort(pcb);
} else {
tcp_timewait_input(pcb);
}
pbuf_free(p);
return;
}
+2 -1
View File
@@ -14,9 +14,10 @@
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
#define TCP_RTO_MIN_MS 3000 // ms, initial RTO
#define TCP_FIN_WAIT_TIMEOUT 20000 // ms
#define TCP_SYN_RCVD_TIMEOUT 20000 // ms
#define TCP_MSL 60000 // ms
#define TCP_MSL 60000 // ms (2*MSL = 120s)
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
+2 -2
View File
@@ -504,7 +504,7 @@ tcp_output(struct tcp_pcb *pcb)
lwip_tcp_trace_record(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd,
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd, "cwd");
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
ntohl(seg->tcphdr->seqno) - pcb->lastack, seg->len, wnd, pcb->cwnd, pcb->snd_wnd);
if (wnd == pcb->snd_wnd && pcb->unacked == NULL && pcb->persist_backoff == 0) {
pcb->persist_cnt = 0;
@@ -534,7 +534,7 @@ tcp_output(struct tcp_pcb *pcb)
lwip_tcp_trace_record(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd,
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd, "snt");
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
ntohl(seg->tcphdr->seqno), seg->len, wnd, pcb->cwnd, err);
if (err != LERR_OK) {
tcp_set_flags(pcb, TF_NAGLEMEMERR);
+2 -1
View File
@@ -3,6 +3,7 @@
#define LWIP_TCP_PRIV_H
#include "lwip_tcp.h"
#include "lwip_tcp_opts.h"
#include <string.h>
// Packed struct support
@@ -240,7 +241,7 @@ void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
#define TCP_RMV(pcbs, npcb) do { \
if(*(pcbs) == (npcb)) { *(pcbs) = (*pcbs)->next; } \
else { struct tcp_pcb *tmp; for(tmp = *(pcbs); tmp != NULL; tmp = tmp->next) { if(tmp->next == (npcb)) { tmp->next = (npcb)->next; break; } } } \
else { struct tcp_pcb *_tmp; for(_tmp = *(pcbs); _tmp != NULL; _tmp = _tmp->next) { if(_tmp->next == (npcb)) { _tmp->next = (npcb)->next; break; } } } \
(npcb)->next = NULL; \
} while(0)
+638
View File
@@ -0,0 +1,638 @@
/*
* msg_transport.c — TCP-сервер транспорта сообщений (local IPC → ETCP)
*
* Принимает TCP-соединения от локальных приложений (протокол msg_transport.h),
* привязывает к remote peer и пересылает данные через etcp_router.
*/
#include "msg_transport.h"
#include "utun_instance.h"
#include "etcp_router.h"
#include "etcp.h"
#include "secure_channel.h"
#include "route_bgp.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#endif
#ifndef DEBUG_CATEGORY_MSGTRANSPORT
#define DEBUG_CATEGORY_MSGTRANSPORT DEBUG_CATEGORY_GENERAL
#endif
/* ============================================================================
* Внутренние структуры
* ============================================================================ */
struct msg_client {
struct msg_client* next;
struct msg_transport* transport;
socket_t fd;
void* socket_id;
uint8_t recv_buffer[MSG_MAX_MSG_SIZE];
uint16_t recv_len;
uint64_t bound_peer_id; /* 0 = не забинден */
uint8_t is_localhost; /* 1 = подключение с loopback */
};
struct msg_transport {
struct UTUN_INSTANCE* instance;
struct UASYNC* ua;
socket_t listen_fd;
void* listen_socket_id;
struct sockaddr_storage bind_addr;
struct msg_client* clients;
uint32_t client_count;
uint32_t max_clients;
uint8_t svc_id;
struct msg_transport_counters counters;
};
/* ============================================================================
* Forward declarations
* ============================================================================ */
static void accept_callback(socket_t fd, void* arg);
static void client_read_callback(socket_t fd, void* arg);
static void client_except_callback(socket_t fd, void* arg);
static void close_client(struct msg_transport* t, struct msg_client* client);
static void handle_client_data(struct msg_transport* t, struct msg_client* client);
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry);
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
const uint8_t* data, size_t data_len, int is_signed);
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
const uint8_t* data, size_t data_len);
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg);
/* ============================================================================
* Инициализация / завершение
* ============================================================================ */
int msg_transport_init(struct msg_transport** out,
struct UTUN_INSTANCE* instance,
struct UASYNC* ua,
struct sockaddr_storage* bind_addr,
uint32_t max_clients,
uint8_t svc_id) {
if (!out || !instance || !ua || !bind_addr) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: invalid args out=%p inst=%p ua=%p addr=%p",
(void*)out, (void*)instance, (void*)ua, (void*)bind_addr);
return -1;
}
if (bind_addr->ss_family == 0) {
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: bind_addr has no family, skipping");
*out = NULL;
return 0;
}
struct msg_transport* t = u_calloc(1, sizeof(*t));
if (!t) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: u_calloc failed");
return -1;
}
t->instance = instance;
t->ua = ua;
t->max_clients = max_clients ? max_clients : 8;
t->svc_id = svc_id;
memcpy(&t->bind_addr, bind_addr, sizeof(*bind_addr));
int family = bind_addr->ss_family;
t->listen_fd = socket(family, SOCK_STREAM, IPPROTO_TCP);
#ifdef _WIN32
if (t->listen_fd == INVALID_SOCKET) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %d", WSAGetLastError());
u_free(t); return -1;
}
u_long nonblock = 1;
ioctlsocket(t->listen_fd, FIONBIO, &nonblock);
int reuse = 1;
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
#else
if (t->listen_fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %s", strerror(errno));
u_free(t); return -1;
}
int reuse = 1;
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
int flags = fcntl(t->listen_fd, F_GETFL, 0);
if (flags >= 0) fcntl(t->listen_fd, F_SETFL, flags | O_NONBLOCK);
#endif
socklen_t addr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
#ifdef _WIN32
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) == SOCKET_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %d", WSAGetLastError());
closesocket(t->listen_fd); u_free(t); return -1;
}
if (listen(t->listen_fd, 5) == SOCKET_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %d", WSAGetLastError());
closesocket(t->listen_fd); u_free(t); return -1;
}
#else
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %s", strerror(errno));
close(t->listen_fd); u_free(t); return -1;
}
if (listen(t->listen_fd, 5) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %s", strerror(errno));
close(t->listen_fd); u_free(t); return -1;
}
#endif
t->listen_socket_id = uasync_add_socket_t(ua, t->listen_fd,
accept_callback, NULL, NULL, t);
if (!t->listen_socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for listen_fd");
#ifdef _WIN32
closesocket(t->listen_fd);
#else
close(t->listen_fd);
#endif
u_free(t); return -1;
}
if (etcp_router_bind(instance, svc_id, on_etcp_recv) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "etcp_router_bind(svc_id=%u) failed", svc_id);
uasync_remove_socket_t(ua, t->listen_fd);
#ifdef _WIN32
closesocket(t->listen_fd);
#else
close(t->listen_fd);
#endif
u_free(t); return -1;
}
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)bind_addr;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on %s:%d svc_id=%u",
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), svc_id);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)bind_addr;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on [%s]:%d svc_id=%u",
ip_to_str(&sin6->sin6_addr, AF_INET6).str, ntohs(sin6->sin6_port), svc_id);
}
*out = t;
return 0;
}
void msg_transport_shutdown(struct msg_transport* t) {
if (!t) return;
while (t->clients) close_client(t, t->clients);
#ifdef _WIN32
if (t->listen_fd != INVALID_SOCKET) {
uasync_remove_socket_t(t->ua, t->listen_fd);
t->listen_socket_id = NULL;
closesocket(t->listen_fd);
t->listen_fd = INVALID_SOCKET;
}
#else
if (t->listen_fd >= 0) {
uasync_remove_socket_t(t->ua, t->listen_fd);
t->listen_socket_id = NULL;
close(t->listen_fd);
t->listen_fd = -1;
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport shutdown complete svc_id=%u", t->svc_id);
memset(t, 0, sizeof(*t));
u_free(t);
}
void msg_transport_get_counters(struct msg_transport* t, struct msg_transport_counters* c) {
if (t && c) memcpy(c, &t->counters, sizeof(*c));
}
/* ============================================================================
* TCP-клиенты: accept / read / close
* ============================================================================ */
static void accept_callback(socket_t fd, void* arg) {
struct msg_transport* t = (struct msg_transport*)arg;
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(client_addr);
#ifdef _WIN32
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd == INVALID_SOCKET) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK)
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %d", err);
return;
}
u_long nonblock = 1;
ioctlsocket(client_fd, FIONBIO, &nonblock);
#else
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %s", strerror(errno));
return;
}
int flags = fcntl(client_fd, F_GETFL, 0);
if (flags >= 0) fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
#endif
if (t->client_count >= t->max_clients) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "max_clients=%u reached, rejecting", t->max_clients);
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
struct msg_client* client = u_calloc(1, sizeof(*client));
if (!client) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "u_calloc(msg_client) failed");
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
client->fd = client_fd;
client->transport = t;
/* Определяем loopback */
if (client_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
client->is_localhost = (ntohl(sin->sin_addr.s_addr) == 0x7F000001u); /* 127.0.0.1 */
} else if (client_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&client_addr;
static const uint8_t loopback_v6[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
client->is_localhost = (memcmp(&sin6->sin6_addr, loopback_v6, 16) == 0);
} else {
client->is_localhost = 0;
}
client->socket_id = uasync_add_socket_t(t->ua, client_fd,
client_read_callback, NULL,
client_except_callback, client);
if (!client->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for client");
u_free(client);
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
client->next = t->clients;
t->clients = client;
t->client_count++;
t->counters.accepted++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client connected (total=%u)", t->client_count);
}
static void client_read_callback(socket_t fd, void* arg) {
struct msg_client* client = (struct msg_client*)arg;
struct msg_transport* t = client->transport;
uint8_t* buf = client->recv_buffer + client->recv_len;
size_t buf_space = MSG_MAX_MSG_SIZE - client->recv_len;
#ifdef _WIN32
int received = recv(fd, (char*)buf, (int)buf_space, 0);
if (received == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
if (err == 10054) { close_client(t, client); return; }
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %d", err);
close_client(t, client);
}
return;
}
#else
ssize_t received = recv(fd, buf, buf_space, 0);
if (received < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %s", strerror(errno));
close_client(t, client);
}
return;
}
#endif
if (received == 0) {
close_client(t, client);
return;
}
client->recv_len += (uint16_t)received;
if (client->recv_len > MSG_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv buffer overflow");
close_client(t, client);
return;
}
handle_client_data(t, client);
}
static void client_except_callback(socket_t fd, void* arg) {
struct msg_client* client = (struct msg_client*)arg;
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "client socket exception");
close_client(client->transport, client);
(void)fd;
}
static void close_client(struct msg_transport* t, struct msg_client* client) {
if (!t || !client) return;
#ifdef _WIN32
if (client->fd != INVALID_SOCKET) {
uasync_remove_socket_t(t->ua, client->fd);
closesocket(client->fd);
}
#else
if (client->fd >= 0) {
uasync_remove_socket_t(t->ua, client->fd);
close(client->fd);
}
#endif
struct msg_client** curr = &t->clients;
while (*curr) {
if (*curr == client) { *curr = client->next; break; }
curr = &(*curr)->next;
}
t->client_count--;
t->counters.closed++;
u_free(client);
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client disconnected (total=%u)", t->client_count);
}
/* ============================================================================
* Обработка входящих сообщений от TCP-клиента
* ============================================================================ */
static void handle_client_data(struct msg_transport* t, struct msg_client* client) {
while (client->recv_len >= MSG_HDR_SIZE) {
struct msg_header* hdr = (struct msg_header*)client->recv_buffer;
if (msg_validate_header(hdr) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "invalid header size=%u type=0x%02x", hdr->size, hdr->type);
send_error(client, MSG_ERR_INVALID_MSG, "Invalid message header");
close_client(t, client);
return;
}
if (client->recv_len < hdr->size) break;
uint8_t* payload = client->recv_buffer + MSG_HDR_SIZE;
uint16_t payload_size = hdr->size - MSG_HDR_SIZE;
switch (hdr->type) {
case MSG_CTL_BIND: {
uint64_t peer_id = hdr->id;
if (peer_id == 0) {
send_error(client, MSG_ERR_INVALID_MSG, "BIND with zero peer_id");
break;
}
/* Проверить конфликт: этот peer_id уже занят другим клиентом */
struct msg_client* other = t->clients;
while (other) {
if (other != client && other->bound_peer_id == peer_id) {
send_error(client, MSG_ERR_ID_TAKEN, "peer_id already bound by another client");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx rejected: already bound",
(unsigned long long)peer_id);
break;
}
other = other->next;
}
if (other) break; /* конфликт */
client->bound_peer_id = peer_id;
send_response(client, MSG_RSP_BIND_OK, peer_id, NULL, 0);
t->counters.bind_count++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx", (unsigned long long)peer_id);
break;
}
case MSG_CTL_UNBIND:
client->bound_peer_id = 0;
t->counters.unbind_count++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "UNBIND");
break;
case MSG_CTL_GET_PUBKEY:
send_response(client, MSG_RSP_PUBKEY, 0,
t->instance->my_keys.public_key, SC_PUBKEY_SIZE);
break;
case MSG_CTL_GET_PRIVKEY:
if (!client->is_localhost) {
send_error(client, MSG_ERR_ACCESS_DENIED, "private key only available from localhost");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PRIVKEY denied: not localhost");
break;
}
send_response(client, MSG_RSP_PRIVKEY, 0,
t->instance->my_keys.private_key, SC_PRIVKEY_SIZE);
break;
case MSG_CTL_GET_PEER_INFO: {
uint64_t peer_id = hdr->id;
struct ROUTE_BGP* bgp = t->instance->bgp;
if (!bgp) {
send_error(client, MSG_ERR_PEER_NOT_FOUND, "BGP not initialized");
break;
}
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, peer_id);
if (!nq) {
send_error(client, MSG_ERR_PEER_NOT_FOUND, "peer not found");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx not found",
(unsigned long long)peer_id);
break;
}
uint8_t buf[SC_PUBKEY_SIZE * 2];
memcpy(buf, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(buf + SC_PUBKEY_SIZE, nq->node.ed25519_public_key, SC_PUBKEY_SIZE);
send_response(client, MSG_RSP_PEER_INFO, peer_id, buf, SC_PUBKEY_SIZE * 2);
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx sent pubkey+edpub",
(unsigned long long)peer_id);
break;
}
case MSG_DATA_SEND: {
uint64_t dst = hdr->id ? hdr->id : client->bound_peer_id;
if (dst == 0) {
send_error(client, MSG_ERR_NOT_BOUND, "Not bound to any peer");
break;
}
if (payload_size == 0) break;
int is_signed = (hdr->options & MSG_OPT_SIGNED) ? 1 : 0;
if (forward_to_peer(t, dst, payload, payload_size, is_signed) != 0) {
send_error(client, MSG_ERR_SEND_FAILED, "Failed to forward message to peer");
t->counters.drops_no_route++;
}
break;
}
default:
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "unknown message type 0x%02x from client", hdr->type);
send_error(client, MSG_ERR_INVALID_MSG, "Unknown message type");
break;
}
uint16_t msg_size = hdr->size;
if (client->recv_len > msg_size)
memmove(client->recv_buffer, client->recv_buffer + msg_size, client->recv_len - msg_size);
client->recv_len -= msg_size;
}
}
/* ============================================================================
* Отправка пиру через etcp_router
* ============================================================================ */
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
const uint8_t* data, size_t data_len, int is_signed) {
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(t->instance, dst_peer_id, t->svc_id);
if (!rconn) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: no route to %016llx", (unsigned long long)dst_peer_id);
return -1;
}
int ret;
if (is_signed) {
ret = etcp_router_conn_send_signed(rconn, data, data_len);
} else {
ret = etcp_router_conn_send(rconn, data, data_len);
}
if (ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: send failed to %016llx (signed=%d)",
(unsigned long long)dst_peer_id, is_signed);
return -1;
}
t->counters.msg_sent_to_peer++;
return 0;
}
/* ============================================================================
* Приём от пира через etcp_router → доставка TCP-клиенту
* ============================================================================ */
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!conn || !entry) return;
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
struct msg_transport* t = inst->msg_t;
if (!t) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: msg_transport not initialized on instance");
queue_dgram_free(entry); queue_entry_free(entry); return;
}
uint8_t* payload = entry->dgram + 1;
size_t payload_len = entry->len - 1;
uint64_t src_node_id = conn->peer_node_id;
DEBUG_TRACE(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: from %016llx len=%zu",
(unsigned long long)src_node_id, payload_len);
/* Ищем клиента привязанного к этому пиру */
struct msg_client* client = t->clients;
while (client) {
if (client->bound_peer_id == src_node_id) break;
client = client->next;
}
if (!client) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: no client bound to %016llx, dropping",
(unsigned long long)src_node_id);
t->counters.drops_no_client++;
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
t->counters.msg_recv_from_peer++;
/* Формируем MSG_DATA_RECV */
size_t total = MSG_HDR_SIZE + payload_len;
uint8_t* buf = u_malloc(total);
if (!buf) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: u_malloc failed");
queue_dgram_free(entry); queue_entry_free(entry); return;
}
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)payload_len, MSG_DATA_RECV, src_node_id, 0);
memcpy(buf + MSG_HDR_SIZE, payload, payload_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
t->counters.msg_sent_to_client++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
/* ============================================================================
* Помощники отправки ответов TCP-клиенту
* ============================================================================ */
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
const uint8_t* data, size_t data_len) {
size_t total = MSG_HDR_SIZE + data_len;
uint8_t* buf = u_malloc(total);
if (!buf) return;
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)data_len, type, id, 0);
if (data_len > 0) memcpy(buf + MSG_HDR_SIZE, data, data_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
}
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg) {
size_t msg_len = strlen(msg);
size_t total = MSG_HDR_SIZE + 1 + msg_len;
uint8_t* buf = u_malloc(total);
if (!buf) return;
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)(1 + msg_len), MSG_RSP_ERROR, 0, 0);
buf[MSG_HDR_SIZE] = error_code;
memcpy(buf + MSG_HDR_SIZE + 1, msg, msg_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
}
+126
View File
@@ -0,0 +1,126 @@
/*
* msg_transport.h — TCP-сервер транспорта сообщений (local IPC → ETCP)
*
* Принимает TCP-соединения от локальных приложений.
* Каждое соединение привязывается (BIND) к конкретному remote peer node_id.
* Сообщения от локального клиента пересылаются пиру через etcp_router,
* сообщения от пира доставляются локальному клиенту.
*
* Формат фрейма на TCP:
* [size:2 (uint16_t, native endian)] [type:1] [id:8 (uint64_t)] [options:1] [data...]
* size включает само поле size.
*/
#ifndef MSG_TRANSPORT_H
#define MSG_TRANSPORT_H
#include <stdint.h>
#include "../lib/socket_compat.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Протокол
* ============================================================================ */
#define MSG_MAX_MSG_SIZE 8192
#define MSG_SIGN_SIZE 64 /* Ed25519 */
/* Options byte bits */
#define MSG_OPT_SIGNED 0x01
/* Типы сообщений */
#define MSG_CTL_BIND 0x01 /* GUI→utun: привязать TCP-соединение к пиру id */
#define MSG_CTL_UNBIND 0x02 /* GUI→utun: отвязать */
#define MSG_CTL_GET_PUBKEY 0x03 /* GUI→utun: запросить публичный ключ */
#define MSG_CTL_GET_PRIVKEY 0x04 /* GUI→utun: запросить приватный ключ (только localhost) */
#define MSG_CTL_GET_PEER_INFO 0x05 /* GUI→utun: запросить ключи пира id (pubkey + ed25519_pubkey) */
#define MSG_DATA_SEND 0x10 /* GUI→utun: отправить сообщение пиру id */
#define MSG_DATA_RECV 0x11 /* utun→GUI: получено сообщение от пира id */
#define MSG_RSP_BIND_OK 0xE1 /* utun→GUI: BIND успешен */
#define MSG_RSP_PUBKEY 0xE2 /* utun→GUI: публичный ключ (32 байта) */
#define MSG_RSP_PRIVKEY 0xE3 /* utun→GUI: приватный ключ (32 байта) */
#define MSG_RSP_PEER_INFO 0xE4 /* utun→GUI: ключи пира (pubkey+edpub=64 байта) */
#define MSG_RSP_ERROR 0xFF /* utun→GUI: ошибка */
/* Коды ошибок для MSG_RSP_ERROR */
#define MSG_ERR_INVALID_MSG 0x01
#define MSG_ERR_NOT_BOUND 0x02
#define MSG_ERR_NO_ROUTE 0x03
#define MSG_ERR_SEND_FAILED 0x04
#define MSG_ERR_ACCESS_DENIED 0x07
#define MSG_ERR_PEER_NOT_FOUND 0x08
#define MSG_ERR_ID_TAKEN 0x06
#pragma pack(push, 1)
struct msg_header {
uint16_t size; /* полный размер сообщения, включая это поле */
uint8_t type; /* тип (MSG_CTL_* / MSG_DATA_* / MSG_RSP_*) */
uint64_t id; /* peer node_id */
uint8_t options; /* битовые флаги (MSG_OPT_*) */
};
#pragma pack(pop)
#define MSG_HDR_SIZE sizeof(struct msg_header) /* 12 */
static inline void msg_build_header(struct msg_header* hdr,
uint16_t payload_size,
uint8_t msg_type,
uint64_t id,
uint8_t options) {
hdr->size = MSG_HDR_SIZE + payload_size;
hdr->type = msg_type;
hdr->id = id;
hdr->options = options;
}
static inline int msg_validate_header(const struct msg_header* hdr) {
if (hdr->size < MSG_HDR_SIZE || hdr->size > MSG_MAX_MSG_SIZE) return -1;
return 0;
}
/* ============================================================================
* Счётчики (диагностика)
* ============================================================================ */
struct UTUN_INSTANCE;
struct UASYNC;
struct msg_transport;
struct msg_transport_counters {
uint64_t accepted;
uint64_t closed;
uint64_t bind_count;
uint64_t unbind_count;
uint64_t msg_sent_to_peer;
uint64_t msg_recv_from_peer;
uint64_t msg_sent_to_client;
uint64_t errors;
uint64_t drops_no_client;
uint64_t drops_no_route;
};
/* ============================================================================
* API
* ============================================================================ */
int msg_transport_init(struct msg_transport** out,
struct UTUN_INSTANCE* instance,
struct UASYNC* ua,
struct sockaddr_storage* bind_addr,
uint32_t max_clients,
uint8_t svc_id);
void msg_transport_shutdown(struct msg_transport* t);
void msg_transport_get_counters(struct msg_transport* t,
struct msg_transport_counters* c);
#ifdef __cplusplus
}
#endif
#endif /* MSG_TRANSPORT_H */
+2 -2
View File
@@ -47,7 +47,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry);
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry, 0);
if (ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed",
(unsigned long long)tr->nat_via_node_id);
@@ -90,7 +90,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry);
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry, 0);
if (send_ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed",
(unsigned long long)entry->src_node_id);
+8 -6
View File
@@ -69,6 +69,8 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->recvpart = NULL;
pn->flush_timer = NULL;
pn->input_waiter_handle.internal = NULL;
return pn;
}
@@ -120,7 +122,7 @@ void pn_deinit(struct PKTNORM* pn) {
// during etcp_connection_close() when drain_and_free_fragment_queue() is called
if (pn->etcp) {
if (pn->etcp->input_queue) {
queue_cancel_wait(pn->etcp->input_queue, &pn->etcp->input_queue->waiter);
queue_waiter_cancel(pn->etcp->input_queue, &pn->input_waiter_handle);
}
if (pn->etcp->output_queue) {
queue_set_callback(pn->etcp->output_queue, NULL, NULL);
@@ -213,7 +215,7 @@ static void packer_cb(struct ll_queue* q, void* arg) {
struct PKTNORM* pn = (struct PKTNORM*)arg;
if (!pn) return;
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "input_q->pn: waiting etcp input threshold");
queue_wait_threshold(pn->etcp->input_queue, 0, 0, etcp_input_ready_cb, pn);
queue_waiter_wait(pn->etcp->input_queue, &pn->input_waiter_handle, etcp_input_ready_cb, pn);
}
// Helper to send block to ETCP as ETCP_FRAGMENT
@@ -239,7 +241,7 @@ static void pn_send_to_etcp(struct PKTNORM* pn) {
frag->ll.memlen = pn->etcp->instance->data_pool->object_size;
DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn->etcp: size=%d memlen=%d frag_size=%d", frag->ll.len, frag->ll.memlen, pn->frag_size);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("NORM->ETCP", pn->data, frag->ll.len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->ETCP", pn->data, frag->ll.len);
queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag);
// Сбросить структуру (dgram передан во фрагмент, не освобождаем)
@@ -284,7 +286,7 @@ static void etcp_input_ready_cb(struct ll_queue* q, void* arg) {
return;
}
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("->NORM", in_dgram->dgram, in_dgram->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "->NORM", in_dgram->dgram, in_dgram->len);
pn_buf_renew(pn);
// DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn_packer: new pkt hdrpos=%d",pn->data_ptr);
@@ -348,7 +350,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
uint8_t* payload = frag->ll.dgram;
uint16_t len = frag->ll.len;
uint16_t ptr = 0;
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("ETCP->NORM", payload, len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "ETCP->NORM", payload, len);
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacking fragment len=%d", len);
@@ -393,7 +395,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
if (pn->recvpart->len == pn->recvpart->memlen) {
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", pn->recvpart->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("NORM->", pn->recvpart->dgram, pn->recvpart->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, pn->recvpart->len);
queue_data_put(pn->output, pn->recvpart);
pn->out_total_pkts++;
pn->out_total_bytes += pn->recvpart->len;
+3
View File
@@ -36,6 +36,9 @@ struct PKTNORM {
struct ll_entry* pending; // Partial processed input entry
uint16_t pending_in_ptr; // Pointer in pending entry
// backpressure:
struct queue_waiter_handle input_waiter_handle; // waiter на etcp->input_queue
// unpacker:
struct ll_entry* recvpart; // блок ожидающий заполнение
// uint16_t recvpart_rem; // сколько байт осталось собрать (0 = ждём заголовок нового пакета)
+477
View File
@@ -0,0 +1,477 @@
## TCP Proxy Protocol — архитектура (v2, без CONNECTED)
### 1. Протокольный формат (общий для client и server)
```
┌──────┬──────┬───────────────────────┬──────────────────────┐
│svc_id│subcmd│ stream_id (4) │ data ... │
│ 1B │ 1B │ │ │
└──────┴──────┴───────────────────────┴──────────────────────┘
HDR = 6 байт
subcmd:
0x01 CONNECT client→server : dest_ip(4)+dest_port(2)
0x03 DATA ↔ bidirectional: payload
0x04 CLOSE ↔ bidirectional: (нет данных)
0x05 ERROR ↔ bidirectional: (нет данных)
```
---
### 2. Диаграмма состояний `tcp_proxy_client_conn`
```
┌──────────────┐
│ ALLOC + │ accept_cb: u_calloc, stream_id++
│ CONNECT │ send CONNECT → exit
│ отправлен │
└──────┬───────┘
│
┌───────────┴───────────┐
│ send успешен? │
└───────────┬───────────┘
Y │ N
┌────────┘ └──────┐
▼ ▼
┌────────────┐ ┌──────────────┐
│ ACTIVE │ │ FREE │
│ traffic │ └──────────────┘
└────┬──┬────┘
│ │
│ └─────────────────────────────────┐
│ │
▼ lwIP FIN (p=NULL) ▼ ERROR от lwIP/exit
┌──────────────┐ ┌──────────────┐
│ TUN_CLOSED │ │ ERROR │
│ tun_closed=1 │ │ error=1 │
│ ↓ send_close │ │ send ERROR→ │
│ после drain │ └──────┬───────┘
│ tx_buf+lwIP │ │
└──────┬───────┘ poll_cb: tcp_abort+free
│
│ recv CLOSE от exit
▼
┌───────────────────┐
│ TUN+REM CLOSED │
│ tun=1 rem=1 │
│ ждём flush буферов│
└────────┬──────────┘
│ poll_cb: pending=0, unsent=NULL, unacked=NULL
│ (или sndbuf==0)
▼
┌──────────────┐
│ CLEANUP │
│ free(pc) │
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_data в ETCP вернул -1 (normalizer полон)
│ TX_BACKPRESSURE│ не tcp_recved() → окно lwIP=0 → recv_cb остановлен
│ tx_buf != NULL │ etcp_router_waiter_register() → ждём освобождения
│ tx_waiter pend │ waiter_cb: retry send_data → tcp_recved → окно открыто
└─────────────────┘
┌─────────────────┐ send_close/send_error вернул -1
│ CLOSE_PENDING │ → retry в poll_cb
│ close_pending=1│
└─────────────────┘
```
---
### 3. Диаграмма состояний `tcp_proxy_server_conn`
```
┌───────────────────┐
│ CREATING │ recv CONNECT
│ socket() создан │ → неблокирующий connect()
│ sock = неблок. │
└────────┬──────────┘
│
┌────────────┴────────────┐
│ connect() результат? │
└────┬──────────┬──────────┘
EINPROGRESS│ │ошибка сразу
▼ ▼
┌──────────┐ ┌──────────────┐
│CONNECTING│ │ ERROR │ send ERROR→client
│ждём write│ │ FREE │
└────┬─────┘ └──────────────┘
│ write_cb: SO_ERROR==0
▼
┌─────────────┐
│ CONNECTED │ connected=1
│ flush pend │ → flush pending_buf в сокет
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
client→DATA │ │ ERROR (sock/pipe)
(sock_send) │ ▼
или pending_buf │ ┌──────────────┐
│ │ │ ERROR │ send ERROR→client
│ │ │ FREE │
│ sock EOF (n=0) │ │
│ │ └──────────────┘
│ ▼
│ ┌──────────────────┐
│ │ SOCK_CLOSED │ sock_closed=1
│ │ out_buf/pause? │ если очереди пусты → send_close
│ │ ждём drain │ иначе ждём retry_cb/waiter_cb
│ └────────┬─────────┘
│ │ очереди опустошены → send_close
│ │ если cli_closed → FREE
│ │
client→CLOSE │
│ │
▼ │
┌──────────────┐ │
│ CLI_CLOSED │ │ cli_closed=1
│ out_buf pend? │◄────────┘ если out_buf пуст → shutdown(SHUT_WR)
│ ждём flush │ иначе ждём sock_retry_cb → shutdown
└──────┬───────┘
│
└──────────────┐
▼
┌──────────────────┐
│ cli && sock │ оба закрыты + очереди пусты
│ очереди пусты │
└────────┬─────────┘
▼
┌──────────────┐
│ FREE │ conn_free(rc)
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_msg в ETCP вернул -1
│ PAUSED │ буферизуем в pause_buf, убираем read_id (пауза чтения)
│ pause_buf>0 │ etcp_router_waiter_register() → ждём освобождения
│ pause_waiter │ waiter_cb: retry send_msg → resume read_id
└─────────────────┘
┌─────────────────┐ CLOSE/ERROR не доставлен через ETCP
│ CLOSE_PENDING │ close_timer с backoff 50..5000 tb → ретрай
│ close_pending=1│
└─────────────────┘
```
---
### 4. DATA FLOW
```
══════════════════════════════════════════════════════════════════════
APP → lwIP → recv_cb → ETCP → handle → socket → DEST
APP ← lwIP ← tcp_write ← ETCP ← read_cb ← socket ← DEST
══════════════════════════════════════════════════════════════════════
CLIENT (узел A) SERVER (узел B)
APP (через TUN) DEST (реальный)
│ │
▼ TCP SYN/ACK (lwIP) ▼ TCP SYN/ACK (OS)
┌──────────┐ ┌──────────┐
│ lwIP │ │ socket │
│ TCP │ │ (неблок) │
└────┬─────┘ └────┬─────┘
│ recv_cb │ read_cb
│ │
┌────▼────────────┐ ┌────▼───────────────┐
│ tcp_proxy │ ETCP │ tcp_proxy │
│ _client │◄═══════════════════════►│ _server │
│ │ │ │
│ ┌────────────┐ │ │ ┌──────────────┐ │
│ │ recv_cb │ │──CONNECT+DATA──► │ │handle_connect│ │
│ │(lwIP→ETCP) │ │ │ │handle_data │ │
│ │ │ │ backpressure: │ └──────┬───────┘ │
│ │ send fail: │ │ waiter на normalizer │ │ sock_send │
│ │ tx_buf+ │ │ │ ┌──────▼───────┐ │
│ │ waiter │ │ │ │ out_buf │ │
│ │ окно lwIP=0│ │ │ │ pending_buf │ │
│ └────────────┘ │ │ └─────────────┘ │
│ │ │ │ send() │
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
│ │handle_data │ │◄────DATA──────── │ │ read_cb │ │
│ │handle_close│ │ │ │(sock→ETCP) │ │
│ └─────┬──────┘ │ │ │ │ │
│ │ │ │ │ send fail: │ │
│ ┌─────▼──────┐ │ │ │ pause_buf+ │ │
│ │ to_lwip │ │ │ │ waiter+ │ │
│ │ (очередь) │ │ │ │ пауза read_id│ │
│ └─────┬──────┘ │ │ └─────────────┘ │
│ │tcp_write│ │ │
│ ▼ │ └───────────────────┘
│ lwIP→TUN→APP │
└─────────────────┘
```
---
### 5. BACKPRESSURE (lwIP→ETCP и socket→ETCP)
```
══════════════════════════════════════════════════════════════════════
Используется встроенный механизм ll_queue threshold waiter:
etcp_router_waiter_register(inst, peer_node_id, &waiter)
→ внутри: queue_waiter_wait(normalizer->input_queue, &waiter)
etcp_router_waiter_cancel(inst, peer_node_id, &waiter)
CLIENT (lwIP → ETCP):
recv_cb(buf, len):
ret = send_data(buf, len)
if ret == 0: tcp_recved(len)
if ret < 0:
tx_buf = copy(buf), tx_len = len
НЕ tcp_recved() → окно lwIP = 0, recv_cb больше не вызывается
etcp_router_waiter_register(&tx_waiter)
tx_waiter callback:
ret = send_data(tx_buf, tx_len)
if ret == 0:
tcp_recved(tx_len) → окно открыто, lwIP возобновляет recv_cb
free(tx_buf)
if tun_closed → send_close()
// если ret < 0 → waiter остаётся, normalizer вызовет снова
SERVER (socket → ETCP):
read_cb(buf, n):
ret = send_msg(DATA, buf, n)
if ret == 0: ok
if ret < 0:
pause_buf = copy(buf), pause_len = n
uasync_remove_socket_t(sock) → пауза чтения с сокета
etcp_router_waiter_register(&pause_waiter)
pause_waiter callback:
ret = send_msg(DATA, pause_buf, pause_len)
if ret == 0:
free(pause_buf)
uasync_add_socket_t(sock) → возобновляем чтение
if sock_closed → send_close()
// если ret < 0 → waiter остаётся
```
---
### 6. Последовательность: HANDSHAKE (упрощённый, без CONNECTED)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
tcp_proxy_client_accept_cb() CONNECT
├─ u_calloc(conn) ────────────────────────────→ tcp_proxy_server_handle_connect()
├─ stream_id++ ├─ socket(AF_INET,SOCK_STREAM)
├─ send_connect() ├─ socket_set_nonblocking()
│ [dest_ip(4)+dest_port(2)] ├─ uasync_add_socket_t(read/write/error)
├─ conn→conns list ├─ connect(неблок.) → EINPROGRESS
│ └─ rc→conns list
▼
ACTIVE — данные идут СРАЗУ (без ожидания CONNECTED)
│
│ DATA ...connect завершён...
├──────────────────────────────────────────────────→ tcp_proxy_server_sock_write_cb()
│ ├─ connected=1
│ DATA └─ flush pending_buf в сокет
├──────────────────────────────────────────────────→
│
│ DATA ←─ tcp_proxy_server_sock_read_cb()
│◄────────────────────────────────────────────── (socket recv → ETCP)
│
└──→ трафик идёт в обе стороны
При ошибке connect:
...connect fail... → tcp_proxy_server_send_error() → client получает ERROR, error=1, cleanup
```
---
### 7. Последовательность: CLOSE (drain очередей → close в конце)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
══ Вариант А: lwIP закрывает первым ══
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close CLOSE
│ если tx_buf pend → waiter досылает ──────────→ tcp_proxy_server_handle_close()
│ потом send_close ├─ cli_closed=1
└─ │ если out_buf пуст → shutdown(SHUT_WR)
│ иначе ждём sock_retry_cb
│
...out_buf дослан...
sock_retry_cb:
shutdown(SHUT_WR)
│
...сокет дочитывает остаток...
│
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если pause_buf пуст → send_close
│ иначе ждём waiter_cb
│
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
poll_cb: tun=1, rem=1, to_lwip пуст, unsent=NULL, unacked=NULL
├─ tcp_close(pcb)
└─ conn_free()
══ Вариант Б: серверный сокет закрывается первым ══
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если out_buf+pause_buf пусты → send_close
│ иначе ждём retry/waiter
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
▼
...приложение закрывает TCP...
│
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close
└─
poll_cb: оба закрыты, буферы пусты → cleanup
══ Вариант В: Ошибка / аномалия ══
ERROR
─────────────────────── (или ←) ──────────
error=1
│
▼
poll_cb: tcp_abort(pcb) → немедленная очистка
или: rc→error=1 → conn_free
══ Вариант Г: conn не найден / closed / error ══
Любой пришедший пакет (DATA/CLOSE/ERROR) для conn в закрытом состоянии:
→ tcp_proxy_*_send_msg(ERROR, stream_id)
→ удалённая сторона получает ERROR и немедленно закрывается
```
---
### 8. Диспетчеризация в `tcp_proxy_client_etcp_recv_cb()`
```
┌────────────────────────────────────────────────────────────────────────┐
│ tcp_proxy_client_etcp_recv_cb() │
│ (единая точка входа для обоих направлений) │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ subcmd == CONNECT? │
│ └── tcp_proxy_server_handle_connect() ←── всегда сервер │
│ │
│ stream_id в server.conns? (если server.enabled) │
│ ├── DATA → tcp_proxy_server_handle_data() │
│ ├── CLOSE → tcp_proxy_server_handle_close() │
│ └── ERROR → rc->error=1; conn_free() │
│ │
│ stream_id в client.conns? (если client proxy != NULL) │
│ ├── DATA → tcp_proxy_client_handle_data() │
│ ├── CLOSE → tcp_proxy_client_handle_close() │
│ └── ERROR → tcp_proxy_client_handle_error() │
│ │
│ Приоритет: │
│ 1. CONNECT всегда интерпретируется сервером │
│ 2. Если server.enabled И stream_id найден в server.conns → server │
│ 3. Иначе если client proxy != NULL → client │
│ 4. Иначе drop │
└────────────────────────────────────────────────────────────────────────┘
```
---
### 9. INIT
```
CLIENT SERVER
─────────────────────────────────────────────────────────
utun_instance.c
│
├─ tcp_proxy_client_create() tcp_proxy_server_init()
│ ├─ tun_init_nat() (TUN iface) ├─ memset(ctx,0)
│ ├─ lwip_tcp_init() (TCP стек) ├─ ctx->enabled = config ...
│ ├─ etcp_router_bind( ├─ etcp_router_bind(
│ │ ETCP_ID_TCP_PROXY, │ ETCP_ID_TCP_PROXY,
│ │ tcp_proxy_client_etcp_recv_cb) │ tcp_proxy_client_etcp_recv_cb)
│ ├─ udp_proxy_init() ├─ udp_proxy_init()
│ └─ icmp_proxy_init() └─ icmp_proxy_init() (если client не активен)
│
└─ ОБА слушают ETCP_ID_TCP_PROXY через один обработчик
```
---
### 10. Поля структур
```
tcp_proxy_client_conn:
stream_id — уникальный ID потока
tun_closed — lwIP/FIN (сторона TUN)
rem_closed — CLOSE от exit
error — немедленная очистка
close_sent — CLOSE/ERROR отправлен
close_pending — отправка не удалась, ретрай
to_lwip — очередь DATA от exit → lwIP (feed_from_transport)
pcb — lwIP tcp_pcb
tx_buf — буфер при backpressure lwIP→ETCP (send_data fail)
tx_len — длина tx_buf
tx_waiter — ll_queue waiter на normalizer.input_queue
tcp_proxy_server_conn:
stream_id — уникальный ID потока
cli_closed — клиент прислал CLOSE
sock_closed — сокет получил EOF
error — ошибка
connected — сокет подключился (connect завершён)
close_pending — CLOSE/ERROR не доставлен, ретрай
pending_buf — буфер данных от клиента до завершения connect
out_buf — буфер отправки в сокет (неблокирующий send, EAGAIN retry)
out_off/out_len — offset/размер out_buf
out_timer — таймер повтора send в сокет
out_backoff — backoff для retry send в сокет
pause_buf — буфер при backpressure socket→ETCP (send_msg fail)
pause_len — длина pause_buf
pause_waiter — ll_queue waiter на normalizer.input_queue
close_timer — таймер повтора CLOSE/ERROR
close_backoff — backoff для retry CLOSE/ERROR
sock — OS сокет к адресату
read_id — handle uasync для событий сокета
```
---
### 11. etcp_router waiter API
```
// etcp_router.h
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
// Внутри:
// conn = route_bgp_find_conn_for_node(peer_node_id)
// queue_waiter_wait(conn->normalizer->input, h)
// queue_waiter_cancel(conn->normalizer->input, h)
//
// normalizer.input_queue уже имеет threshold=0 (ждёт полного освобождения)
// через queue_set_threshold(input_queue, 0, 0) в etcp.c
```
+8 -8
View File
@@ -120,7 +120,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
memcpy(e->dgram + 20, &icmp_hdr->icmp_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e);
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e, 0);
if (ret != 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: etcp_route_send reply failed: %d", ret);
else DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply forwarded to client %016llx", (unsigned long long)r->client_node_id);
u_free(r);
@@ -158,7 +158,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
etcp_route_send(inst, client_node_id, e);
etcp_route_send(inst, client_node_id, e, 0);
} else queue_entry_free(e);
}
} else {
@@ -223,7 +223,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
memcpy(e->dgram + 20, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, exit_node_id, e);
return etcp_route_send(inst, exit_node_id, e, 0);
}
// ====================================================================
@@ -232,12 +232,12 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
const uint8_t* payload, size_t payload_len) {
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) {
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
!inst ? "inst" : !inst->tcp_proxy ? "tcp_proxy" : "TUN");
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
return -1;
}
struct tun_if* tun = inst->tcp_proxy->tun;
struct tun_if* tun = inst->tcp_proxy_client->tun;
size_t icmp_len = ICMP_MINLEN + payload_len;
size_t pkt_len = 20 + icmp_len;
@@ -270,8 +270,8 @@ int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
e->dgram = u_malloc(1 + pkt_len);
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len;
u_free(pkt);
queue_data_put(tun->input_queue, e);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply delivered to TUN id=0x%04x seq=%u", echo_id, echo_seq);
queue_data_put(tun->input_queue, e);
return 0;
}
@@ -306,7 +306,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
if (!ctx) return -1;
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID;
ctx->request_timeout_tb = ICMP_TIMEOUT_TB;
ctx->is_exit = inst->remote_proxy.enabled;
ctx->is_exit = inst->tcp_proxy_server.enabled;
ctx->test_loopback = 0;
ctx->expire_timer = NULL;
g_icmp_ctx = ctx;
File renamed without changes.
+106
View File
@@ -0,0 +1,106 @@
Как узнать, что TCP полностью завершился?
В raw API прямого callback'а вида:
on_connection_closed()
нет.
Обычно используют собственное состояние:
struct conn {
bool closing;
};
и после успешного:
tcp_close(pcb);
conn->closing = true;
считают соединение закрытым для приложения.
Сам TCP завершится позже внутри lwIP.
А вызовется ли err callback?
Обычно нет.
tcp_err()
вызывается при ненормальном завершении:
RST
abort
потеря PCB
сетевые ошибки
но не при штатном FIN/ACK закрытии.
Нужно ли освобождать память после tcp_close()?
Сам pcb освобождать нельзя:
tcp_close(pcb);
/* НЕЛЬЗЯ */
mem_free(pcb);
lwIP сделает это самостоятельно.
Но ваши данные (arg) нужно освободить самостоятельно.
Обычно так:
struct my_conn *c = arg;
tcp_arg(pcb, NULL);
free(c);
tcp_close(pcb);
или
free(c);
tcp_close(pcb);
pcb = NULL;
если после этого код больше не обращается к c.
Безопасная практика
После успешного tcp_close():
if (tcp_close(pcb) == ERR_OK) {
pcb = NULL;
}
и считать объект недействительным.
Потому что lwIP может уничтожить его в любой момент по мере завершения TCP state machine.
Типичный шаблон
static void close_conn(struct my_conn *c)
{
err_t err;
err = tcp_close(c->pcb);
if (err == ERR_OK) {
c->pcb = NULL;
free(c);
} else {
/* ERR_MEM -> попробуем позже */
}
}
Но если free(c) делается сразу, нужно быть уверенным, что никакие callback'и lwIP больше не используют arg. Часто перед закрытием сначала снимают callback'и:
tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_err(pcb, NULL);
а затем вызывают tcp_close().
Это особенно важно в сложных приложениях с собственными структурами соединений.
+644
View File
@@ -0,0 +1,644 @@
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
#include "tcp_proxy_client.h"
#include "lwip_tcp/lwip_tcp.h"
#include "lwip_tcp/lwip_tcp_priv.h"
#include "lwip_tcp/lwip_tcp_opts.h"
#include "config_parser.h"
#include "tun_if.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "tcp_proxy_server.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <arpa/inet.h>
#endif
#ifndef INADDR_ANY
#define INADDR_ANY 0
#endif
// ====================================================================
// Предварительные объявления
// ====================================================================
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
static void tcp_proxy_client_err_cb(void *arg, err_t err);
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb);
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc);
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc);
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg);
// ====================================================================
// Помощник ll_entry
// ====================================================================
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
struct ll_entry* e = queue_entry_new_from_pool(pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: pool exhausted"); return NULL; }
e->len = 0; e->dgram = NULL;
if (len > 0) {
uint8_t* buf = u_malloc(len);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
memcpy(buf, data, len); e->dgram = buf; e->len = len;
}
return e;
}
// ====================================================================
// Протокол: сборка и отправка сообщений прокси через ETCP
// ====================================================================
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, dst, e, force);
}
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_conn* pc) {
uint8_t buf[6];
memcpy(buf, pc->dest_ip, 4); memcpy(buf + 4, &pc->dest_port, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3],
ntohs(pc->dest_port), (unsigned long long)pc->proxy->via_node_id);
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6, 1);
}
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len, 0);
}
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x", pc->stream_id);
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0, 1);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
pc->close_sent = 1;
return 0;
}
static int tcp_proxy_client_send_error(struct tcp_proxy_client_conn* pc) {
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0, 1);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
pc->close_sent = 1;
return 0;
}
static int tcp_proxy_client_send_fin(struct tcp_proxy_client_conn* pc) {
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY FIN RELAY sid=%08x", pc->stream_id);
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_FIN, pc->stream_id, NULL, 0, 1);
}
// ====================================================================
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
// ====================================================================
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)arg;
(void)src_ip; (void)dst_ip;
uint16_t len = p->tot_len;
if (len > 2000) return LERR_BUF;
uint8_t buf[2002];
if (proxy->tun) {
pbuf_copy_partial(p, buf, len, 0);
ssize_t wr = tun_platform_write(proxy->tun, buf, len);
if (wr != (ssize_t)len) {
uint16_t ip_total = ((uint16_t)buf[2] << 8) | buf[3];
uint32_t sip, dip; memcpy(&sip, buf + 12, 4); memcpy(&dip, buf + 16, 4);
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TUN_WRITE_ERR ret=%zd len=%u ip_total=%u", wr, len, ip_total);
}
}
return LERR_OK;
}
// ====================================================================
// Обработчик не-TCP (UDP/ICMP прокси)
// ====================================================================
static int tcp_proxy_client_handle_non_tcp(struct tcp_proxy_client* p, uint8_t* buf, size_t len) {
if (len < 20) return 0;
uint8_t ip_ver = (buf[0] >> 4) & 0xF;
if (ip_ver != 4) return 0;
uint8_t proto = buf[9];
if (proto == IPPROTO_UDP) {
if (len < 28) return 0;
uint32_t src_ip, dst_ip; uint16_t src_port, dst_port;
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
memcpy(&src_port, buf + 20, 2); memcpy(&dst_port, buf + 22, 2);
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, buf + 28, len - 28);
return 1;
}
if (proto == IPPROTO_ICMP) {
if (len < 28) return 0;
uint8_t icmp_type = buf[20];
if (icmp_type != 8) return 0;
uint32_t src_ip, dst_ip;
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
uint16_t icmp_id, icmp_seq;
memcpy(&icmp_id, buf + 24, 2); memcpy(&icmp_seq, buf + 26, 2);
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, src_ip, icmp_id, icmp_seq, buf + 28, len - 28);
return 1;
}
return 0;
}
// ====================================================================
// Помощник: передача данных из очереди to_lwip в lwIP TCP
// ====================================================================
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc) {
if (!pc->to_lwip || !pc->pcb) return;
if (pc->rem_closed) return;
int sent_any = 0;
uint32_t q_pre = queue_entry_count(pc->to_lwip);
while (1) {
uint16_t space = tcp_sndbuf(pc->pcb);
if (space < TCP_MSS / 2) break;
struct ll_entry *e = queue_data_get(pc->to_lwip);
if (!e) break;
if (e->len <= space) {
err_t ret = tcp_write(pc->pcb, e->dgram, e->len, TCP_WRITE_FLAG_COPY);
if (ret == LERR_OK) { sent_any = 1; queue_dgram_free(e); queue_entry_free(e); }
else { queue_data_put_first(pc->to_lwip, e); break; }
} else {
queue_data_put_first(pc->to_lwip, e);
break;
}
queue_resume_callback(pc->to_lwip);
}
queue_resume_callback(pc->to_lwip);
if (sent_any) {
uint32_t unsent = 0; struct tcp_seg* s;
for (s = pc->pcb->unsent; s; s = s->next) unsent++;
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY FEED exit->client sid=%08x fed=%u q=%u->%u snd_wnd=%u cwnd=%u unsent=%u",
pc->stream_id, sent_any, q_pre, queue_entry_count(pc->to_lwip), pc->pcb->snd_wnd, pc->pcb->cwnd, unsent);
tcp_output(pc->pcb);
}
}
// ====================================================================
// lwIP TCP коллбэки
// ====================================================================
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
struct tcp_proxy_client *p = (struct tcp_proxy_client *)arg;
if (err != LERR_OK || !newpcb) return LERR_ABRT;
struct tcp_proxy_client_conn *pc = u_calloc(1, sizeof(struct tcp_proxy_client_conn));
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: accept alloc failed"); return LERR_MEM; }
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
int i;
for (i = 0; i < p->mapping_count; i++) {
if (p->mappings[i].local_port == newpcb->local_port) {
struct in_addr ra; ra.s_addr = inet_addr(p->mappings[i].remote_ip);
memcpy(pc->dest_ip, &ra.s_addr, 4);
pc->dest_port = htons(p->mappings[i].remote_port);
break;
}
}
if (i == p->mapping_count) {
memcpy(pc->dest_ip, &newpcb->local_ip, 4);
pc->dest_port = htons(newpcb->local_port);
}
tcp_arg(newpcb, pc);
tcp_recv(newpcb, tcp_proxy_client_recv_cb);
tcp_sent(newpcb, tcp_proxy_client_sent_cb);
tcp_err(newpcb, tcp_proxy_client_err_cb);
tcp_poll(newpcb, tcp_proxy_client_poll_cb, 2);
tcp_nagle_disable(newpcb);
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); tcp_arg(newpcb, NULL); tcp_abort(newpcb); queue_free(pc->to_lwip); u_free(pc); return LERR_MEM; }
pc->next = p->conns; p->conns = pc; p->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
pc->stream_id, newpcb->local_port, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), p->conn_count, newpcb->snd_wnd, newpcb->mss);
return LERR_OK;
}
// ====================================================================
// Backpressure: waiter callback при освобождении normalizer очереди
// ====================================================================
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
if (!pc || !pc->proxy || pc->rem_closed || !pc->tx_buf) return;
int ret = tcp_proxy_client_send_data(pc, pc->tx_buf, pc->tx_len);
if (ret == 0) {
if (pc->pcb) tcp_recved(pc->pcb, pc->tx_len);
u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0;
if (pc->fin_local && !pc->close_sent && !pc->close_pending) {
if (pc->fin_remote || pc->rem_closed)
tcp_proxy_client_send_close(pc);
else
tcp_proxy_client_send_fin(pc);
}
}
}
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) { if (p) pbuf_free(p); return LERR_OK; }
if (p == NULL || err != LERR_OK) {
pc->fin_local = 1;
if (p == NULL && err == ERR_OK) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN sid=%08x pcb_state=%u sndbuf=%u cwnd=%u unsent=%p unacked=%p",
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
(void*)pcb->unsent, (void*)pcb->unacked);
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
if (!pc->tx_buf) {
tcp_proxy_client_send_fin(pc);
if (pc->fin_remote && !pc->close_sent && !pc->close_pending)
tcp_proxy_client_send_close(pc);
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR recv sid=%08x pcb_state=%u err=%d",
pc->stream_id, pcb->state, err);
pc->error = 1;
tcp_proxy_client_send_error(pc);
}
return LERR_OK;
}
uint16_t len = p->tot_len;
if (pc->error || pc->rem_closed) {
tcp_recved(pcb, len); pbuf_free(p); return LERR_OK;
}
if (pc->tx_buf) { pbuf_free(p); return LERR_OK; }
uint8_t *data = u_malloc(len);
if (data) {
pbuf_copy_partial(p, data, len, 0);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
int ret = tcp_proxy_client_send_data(pc, data, len);
if (ret == 0) { tcp_recved(pcb, len); u_free(data); }
else {
pc->tx_buf = data; pc->tx_len = len;
etcp_router_waiter_register(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter,
tcp_proxy_client_tx_waiter_cb, pc);
}
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
pbuf_free(p);
return LERR_OK;
}
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
(void)len;
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
tcp_proxy_client_feed_from_transport(pc);
return LERR_OK;
}
static void tcp_proxy_client_err_cb(void *arg, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY tcp_proxy_client_err_cb: pc=%p proxy=%p rem_closed=%d err=%d", (void*)pc, pc ? (void*)pc->proxy : NULL, pc ? pc->rem_closed : 0, err); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: error %d sid=%08x pcb_state=%u fin_local=%d rem_closed=%d",
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->fin_local, pc->rem_closed);
pc->pcb = NULL;// pcb уже уничтожен стеком lwip
pc->error = 1;
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
}
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
if (pc->error) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x", pc->stream_id);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_abort(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc);
return LERR_OK;
}
if (pc->close_pending) {
if (pc->error) tcp_proxy_client_send_error(pc);
else tcp_proxy_client_send_close(pc);
}
return LERR_OK;
}
// ====================================================================
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
// ====================================================================
static void tcp_proxy_client_ensure_outbound_listen(struct tcp_proxy_client* p, uint16_t dport_net) {
uint16_t dport_host = ntohs(dport_net);
struct tcp_pcb* lp = p->lwip->listen_pcbs;
while (lp) { if (lp->local_port == dport_host) return; lp = lp->next; }
struct tcp_pcb* lpcb = tcp_new(p->lwip);
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: tcp_new failed for dynamic listen port %u", dport_host); return; }
tcp_bind(lpcb, INADDR_ANY, dport_net);
struct tcp_pcb* listen_pcb = tcp_listen(lpcb);
if (listen_pcb) {
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: dynamic listen on port %u", dport_host);
}
}
// ====================================================================
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
// ====================================================================
static void tcp_proxy_client_tun_input(struct ll_queue* q, void* arg) {
struct tcp_proxy_client* p = (struct tcp_proxy_client*)arg;
struct ll_entry* entry = queue_data_get(q);
if (!entry) return;
if (entry->dgram && entry->len > 1) {
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
if (len > 20 && len <= 2000) {
if (!tcp_proxy_client_handle_non_tcp(p, ip, len)) {
uint8_t proto = ip[9];
if (proto == IPPROTO_TCP) {
uint16_t ip_hdr_len = (ip[0] & 0x0F) * 4;
uint16_t ip_total = ((uint16_t)ip[2] << 8) | ip[3];
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len && len >= ip_total) {
uint16_t dport_net; memcpy(&dport_net, ip + ip_hdr_len + 2, 2);
tcp_proxy_client_ensure_outbound_listen(p, dport_net);
uint32_t src_ip, dst_ip;
memcpy(&src_ip, ip + 12, 4); memcpy(&dst_ip, ip + 16, 4);
uint16_t tcp_len = ip_total - ip_hdr_len;
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
if (pb) { pbuf_take(pb, ip + ip_hdr_len, tcp_len); lwip_tcp_input(p->lwip, pb, src_ip, dst_ip); }
}
}
}
}
}
queue_dgram_free(entry); queue_entry_free(entry); queue_resume_callback(q);
}
// ====================================================================
// Очистка tcp_proxy_client_conn
// ====================================================================
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
if (!pc) return;
struct tcp_proxy_client *p = pc->proxy;
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FREE sid=%08x total_conns=%d to_lwip_q=%u",
pc->stream_id, p->conn_count, pending);
if (pc->close_pending && p && p->inst) {
pc->close_pending = 0;
uint8_t subcmd = pc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
tcp_proxy_client_send_msg(p->inst, p->via_node_id, subcmd, pc->stream_id, NULL, 0, 1);
}
struct tcp_proxy_client_conn **prev = &p->conns;
while (*prev) { if (*prev == pc) { *prev = pc->next; p->conn_count--; break; } prev = &(*prev)->next; }
if (pc->to_lwip) {
struct ll_entry *e;
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
queue_free(pc->to_lwip); pc->to_lwip = NULL;
}
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
if (pc->proxy && pc->proxy->inst) etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
u_free(pc);
}
// ====================================================================
// Обработчики входящих сообщений (сторона клиента)
// ====================================================================
static struct tcp_proxy_client_conn* tcp_proxy_client_find_conn(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc;
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
return NULL;
}
static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc || pc->rem_closed || pc->error) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — no conn/closed, dropping", stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
if (data_len > 0) {
struct ll_entry* e = tcp_proxy_client_entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
if (e) queue_data_put(pc->to_lwip, e);
tcp_proxy_client_feed_from_transport(pc);
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb_state=%u", stream_id, pc->fin_local, pc->pcb ? pc->pcb->state : 0);
pc->rem_closed = 1;
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_close(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc);
}
static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
if (pc->pcb) {
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_close(pc->pcb);
pc->pcb = NULL;
}
pc->rem_closed = 1;
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
tcp_proxy_client_conn_free(pc);
}
static void tcp_proxy_client_handle_fin(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc || !pc->pcb) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN FROM exit sid=%08x pcb_state=%u — shutdown write (send FIN to local)", stream_id, pc->pcb->state);
pc->fin_remote = 1;
if (pc->pcb->state != TIME_WAIT && pc->pcb->state != CLOSED) {
tcp_shutdown(pc->pcb, 0, 1);
tcp_sent(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
}
if (pc->fin_local && !pc->close_sent && !pc->close_pending)
tcp_proxy_client_send_close(pc);
}
// ====================================================================
// ETCP коллбэк клиентской стороны (принимает DATA/CLOSE/ERROR/FIN от сервера)
// ====================================================================
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY_CLIENT) {
uint64_t peer_id;
memcpy(&peer_id, entry->dgram + 1, 8);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing client conns for peer",
(unsigned long long)peer_id);
if (proxy) {
struct tcp_proxy_client_conn *pc, *next;
for (pc = proxy->conns; pc; pc = next) { next = pc->next; tcp_proxy_client_conn_free(pc); }
proxy->conns = NULL; proxy->conn_count = 0;
}
}
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
if (proxy) {
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — client conn not found, silent drop", subcmd, 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), "entry_pool");
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
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
+474
View File
@@ -0,0 +1,474 @@
// 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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc);
} else if (pend) {
tcp_conn_set_flushed(tc, on_flushed_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
(int)tc->sock, rc->stream_id, pend);
} else {
send_fin(rc);
DEBUG_DEBUG(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_DEBUG(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_DEBUG(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;
rc->diag_timer = NULL;
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_DEBUG(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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
(int)rc->tc->sock, rc->stream_id);
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
&rc->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_DEBUG(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_DEBUG(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_DEBUG(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_DEBUG(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, inst->config->global.tcp_recv_buf, 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) {
if (data_len > rc->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
data_len, rc->tc->data_pool->object_size, stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
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_DEBUG(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_DEBUG(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_DEBUG(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_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, 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
+6 -6
View File
@@ -40,7 +40,7 @@ static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id
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];
uint8_t buf[1600];
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;
@@ -64,7 +64,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n);
e->len = UDP_PROXY_HDR_SIZE + n;
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e);
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e, 0);
}
// ====================================================================
@@ -169,7 +169,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
memcpy(e->dgram + 20, &dst_port, 2);
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len);
e->len = UDP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, exit_node_id, e);
return etcp_route_send(inst, exit_node_id, e, 0);
}
// ====================================================================
@@ -179,7 +179,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t src_ip, uint16_t src_port,
uint32_t dst_ip, uint16_t dst_port,
const uint8_t* payload, size_t payload_len) {
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) return -1;
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) return -1;
// Собрать IP/UDP ответный пакет
size_t ip_len = 20 + 8 + payload_len;
@@ -208,7 +208,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
e->dgram = u_malloc(1 + ip_len);
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, ip_len); e->len = 1 + ip_len;
u_free(pkt);
queue_data_put(inst->tcp_proxy->tun->input_queue, e);
queue_data_put(inst->tcp_proxy_client->tun->input_queue, e);
return 0;
}
@@ -242,7 +242,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
ctx->inst = inst; ctx->ua = ua;
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
ctx->expire_timer = NULL;
ctx->is_exit = inst->remote_proxy.enabled;
ctx->is_exit = inst->tcp_proxy_server.enabled;
g_udp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);
File renamed without changes.
-387
View File
@@ -1,387 +0,0 @@
// remote_proxy.c — Удаленный TCP прокси (exit node)
#include "remote_proxy.h"
#include "tcp_proxy.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static struct remote_proxy_ctx* g_rp_ctx = NULL;
static void rp_sock_read_cb(socket_t sock, void* arg);
static void rp_sock_write_cb(socket_t sock, void* arg);
static void rp_sock_error_cb(socket_t sock, void* arg);
static void rp_sock_retry_cb(void* arg);
static void rp_sock_pause_cb(void* arg);
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len);
void rp_conn_free(struct remote_proxy_conn* rc);
#define RP_NORMALIZER_Q_THRESHOLD 64
static int rp_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rp_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rp_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, dst, e);
}
static int rp_send_connected(struct UTUN_INSTANCE* inst, uint64_t dst, uint32_t sid,
uint16_t local_port, uint8_t status) {
uint8_t buf[3];
memcpy(buf, &local_port, 2); buf[2] = status;
return rp_send_msg(inst, dst, TCP_PROXY_SUBCMD_CONNECTED, sid, buf, 3);
}
static int rp_conn_total(struct remote_proxy_conn* rc) {
if (!rc || !rc->ctx) return 0;
int n = 0; struct remote_proxy_conn* c;
for (c = rc->ctx->conns; c; c = c->next) n++;
return n;
}
// ====================================================================
// Неблокирующий send с EAGAIN и таймером повтора
// ====================================================================
static int rp_sock_try_send(struct remote_proxy_conn* rc) {
if (!rc || rc->sock == SOCKET_INVALID || !rc->out_buf) return 0;
while (rc->out_off < rc->out_len) {
ssize_t n = send(rc->sock, rc->out_buf + rc->out_off, rc->out_len - rc->out_off, MSG_NOSIGNAL);
if (n > 0) {
rc->out_off += n;
if (rc->out_off >= rc->out_len) {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
return 0;
}
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND_ERR fd=%d sid=%08x err=%s", (int)rc->sock, rc->stream_id, strerror(errno));
return -1;
}
return 0;
}
static void rp_sock_retry_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || !rc->out_buf) { rc->out_timer = NULL; return; }
int ret = rp_sock_try_send(rc);
if (ret == 0) {
rc->out_timer = NULL; rc->out_backoff = 0;
} else if (ret == 1) {
rc->out_backoff = rc->out_backoff < 20 ? 20 : rc->out_backoff * 2;
if (rc->out_backoff > 5000) rc->out_backoff = 5000;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
rc->out_timer = NULL; rc->out_backoff = 0;
}
}
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len) {
if (!rc || !data || len == 0) return;
if (rc->out_buf) {
size_t new_len = rc->out_len + len;
uint8_t* new_buf = u_realloc(rc->out_buf, new_len);
if (!new_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND realloc(%zu) failed sid=%08x", new_len, rc->stream_id); return; }
memcpy(new_buf + rc->out_len, data, len);
rc->out_buf = new_buf; rc->out_len = new_len;
return;
}
rc->out_buf = u_malloc(len);
if (!rc->out_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND malloc(%zu) failed sid=%08x", len, rc->stream_id); return; }
memcpy(rc->out_buf, data, len); rc->out_len = len; rc->out_off = 0;
int ret = rp_sock_try_send(rc);
if (ret == 0) return;
if (ret == 1) {
rc->out_backoff = 10;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
}
}
static void rp_sock_pause_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) { rc->pause_timer = NULL; return; }
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
int qcnt = -1;
if (inst) qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
rc->pause_backoff = rc->pause_backoff * 2;
if (rc->pause_backoff > 5000) rc->pause_backoff = 5000;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
if (rc->pause_buf && rc->pause_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH fd=%d sid=%08x len=%zu qcnt=%d", (int)rc->sock, rc->stream_id, rc->pause_len, qcnt);
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->pause_buf, rc->pause_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0; rp_conn_free(rc); return;
}
u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0;
}
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
rc->pause_timer = NULL;
}
// ====================================================================
// Socket callbacks
// ====================================================================
static void rp_sock_read_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
uint8_t buf[8192]; ssize_t n = recv(rc->sock, buf, sizeof(buf), 0);
if (n > 0) {
if (rc->cli_closed || rc->error) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x — inst is NULL, drop", (int)rc->sock, rc->stream_id); return; }
int qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE fd=%d sid=%08x len=%zd qcnt=%d — normalizer переполнен, пауза", (int)rc->sock, rc->stream_id, n, qcnt);
rc->pause_buf = u_malloc(n);
if (rc->pause_buf) { memcpy(rc->pause_buf, buf, n); rc->pause_len = n; }
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE malloc(%zd) failed sid=%08x", n, rc->stream_id);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
rc->pause_backoff = 50;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%zd total=%d", (int)rc->sock, rc->stream_id, n, rp_conn_total(rc));
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, buf, (size_t)n) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; rp_conn_free(rc); return;
}
} else if (n == 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:EOF fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d",
(int)rc->sock, rc->stream_id, rp_conn_total(rc), rc->cli_closed, rc->sock_closed);
rc->sock_closed = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:EOF fd=%d sid=%08x — inst is NULL, can't send CLOSE", (int)rc->sock, rc->stream_id);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
if (rc->cli_closed) rp_conn_free(rc);
} else if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x errno=%d %s total=%d",
(int)rc->sock, rc->stream_id, errno, strerror(errno), rp_conn_total(rc));
rc->error = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x — inst is NULL, can't send ERROR", (int)rc->sock, rc->stream_id);
rp_conn_free(rc);
}
}
static void rp_sock_write_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID || rc->connected) return;
int err = 0; socklen_t len = sizeof(err);
if (getsockopt(rc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
rc->connected = 1;
struct sockaddr_in local; socklen_t llen = sizeof(local);
uint16_t local_port = 0;
if (getsockname(rc->sock, (struct sockaddr*)&local, &llen) == 0) local_port = local.sin_port;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CONN fd=%d sid=%08x local=%d dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, rc->stream_id, ntohs(local_port),
rc->dest_ip[0], rc->dest_ip[1], rc->dest_ip[2], rc->dest_ip[3], ntohs(rc->dest_port),
rp_conn_total(rc));
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, local_port, TCP_PROXY_CONNECTED_OK);
if (rc->pending_buf && rc->pending_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSH fd=%d sid=%08x len=%zu", (int)rc->sock, rc->stream_id, rc->pending_len);
rp_sock_send(rc, rc->pending_buf, rc->pending_len);
u_free(rc->pending_buf); rc->pending_buf = NULL; rc->pending_len = 0;
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FAIL fd=%d sid=%08x err=%d %s",
(int)rc->sock, rc->stream_id, err, err ? strerror(err) : "unknown");
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
rp_conn_free(rc);
}
}
static void rp_sock_error_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERR fd=%d sid=%08x", (int)rc->sock, rc->stream_id);
rc->error = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERR fd=%d sid=%08x — inst is NULL", (int)rc->sock, rc->stream_id);
rp_conn_free(rc);
}
void rp_conn_free(struct remote_proxy_conn* rc) {
if (!rc) return;
int total = rp_conn_total(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d error=%d",
(int)rc->sock, rc->stream_id, total, rc->cli_closed, rc->sock_closed, rc->error);
if (rc->ctx) {
struct remote_proxy_conn** prev = &rc->ctx->conns;
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
}
if (rc->sock != SOCKET_INVALID) {
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
socket_close_wrapper(rc->sock); rc->sock = SOCKET_INVALID;
}
if (rc->pending_buf) { u_free(rc->pending_buf); rc->pending_buf = NULL; }
if (rc->out_timer) { uasync_cancel_timeout(rc->ua, rc->out_timer); rc->out_timer = NULL; }
if (rc->out_buf) { u_free(rc->out_buf); rc->out_buf = NULL; }
if (rc->pause_timer) { uasync_cancel_timeout(rc->ua, rc->pause_timer); rc->pause_timer = NULL; }
if (rc->pause_buf) { u_free(rc->pause_buf); rc->pause_buf = NULL; }
u_free(rc);
}
// ====================================================================
// Public API
// ====================================================================
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id) {
struct remote_proxy_conn* c;
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
return NULL;
}
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id) {
if (!inst || !inst->remote_proxy.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
uint8_t* dest_ip = entry->dgram + TCP_PROXY_HDR_SIZE;
uint16_t dest_port = 0; memcpy(&dest_port, dest_ip + 4, 2);
struct remote_proxy_conn* rc = u_calloc(1, sizeof(struct remote_proxy_conn));
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: u_calloc failed for sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->ctx = ctx; rc->stream_id = stream_id; rc->peer_node_id = src_node_id;
memcpy(rc->dest_ip, dest_ip, 4); rc->dest_port = dest_port;
rc->ua = inst->ua; rc->sock = SOCKET_INVALID;
rc->sock = socket(AF_INET, SOCK_STREAM, 0);
if (rc->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(rc->sock);
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
if (!rc->read_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: uasync_add_socket_t failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, dest_ip, 4); addr.sin_port = dest_port;
int ret = connect(rc->sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: connect() to %d.%d.%d.%d:%d failed: %s",
dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], ntohs(dest_port), strerror(errno));
rp_send_connected(inst, src_node_id, stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
uasync_remove_socket_t(rc->ua, rc->sock); socket_close_wrapper(rc->sock); u_free(rc);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
rc->next = ctx->conns; ctx->conns = rc;
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn* rc = remote_proxy_find_conn(ctx, stream_id);
if (!rc || rc->sock == SOCKET_INVALID) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "RP handle_data drop: нет соединения для sid=%08x, шлём CLOSE", stream_id);
if (conn) rp_send_msg(inst, conn->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, stream_id, NULL, 0);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
if (rc->error) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (rc->connected) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:SEND fd=%d sid=%08x len=%zu total=%d",
(int)rc->sock, stream_id, data_len, rp_conn_total(rc));
if (data_len > 0) rp_sock_send(rc, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
} else {
size_t new_len = rc->pending_len + data_len;
uint8_t* new_buf = u_realloc(rc->pending_buf, new_len);
if (new_buf) {
if (data_len > 0) memcpy(new_buf + rc->pending_len, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
rc->pending_buf = new_buf; rc->pending_len = new_len;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "RP buffer sid=%08x added=%zu total=%zu", stream_id, data_len, new_len);
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP buffer realloc(%zu) failed sid=%08x", new_len, stream_id);
}
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn** prev = &ctx->conns;
while (*prev) {
struct remote_proxy_conn* rc = *prev;
if (rc->stream_id == stream_id) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d sock_closed=%d connected=%d",
(int)rc->sock, stream_id, rp_conn_total(rc), rc->sock_closed, rc->connected);
rc->cli_closed = 1;
if (rc->sock != SOCKET_INVALID && rc->connected == 1) shutdown(rc->sock, SHUT_WR);
if (rc->sock_closed) rp_conn_free(rc);
return;
}
prev = &rc->next;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "remote_proxy: CLOSE sid=%08x — no conn", stream_id);
}
int remote_proxy_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
memset(ctx, 0, sizeof(*ctx));
if (!inst->config) return 0;
ctx->enabled = inst->config->global.remote_proxy_enabled;
ctx->inst = inst;
if (!ctx->enabled) return 0;
g_rp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_etcp_recv_cb);
if (!inst->config->global.tcp_proxy_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy initialized node=%016llx", (unsigned long long)inst->node_id);
return 0;
}
void remote_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroying: conn_count=%d", ctx->conn_count);
struct remote_proxy_conn* rc = ctx->conns;
while (rc) { struct remote_proxy_conn* next = rc->next; rp_conn_free(rc); rc = next; }
ctx->conns = NULL; ctx->enabled = 0;
if (g_rp_ctx == ctx) g_rp_ctx = NULL;
udp_proxy_destroy(inst);
icmp_proxy_destroy(inst);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroyed");
}
-76
View File
@@ -1,76 +0,0 @@
// remote_proxy.h — Удаленный TCP прокси (exit node)
#ifndef REMOTE_PROXY_H
#define REMOTE_PROXY_H
#include <stdint.h>
#include "../lib/socket_compat.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
struct remote_proxy_ctx;
#define TCP_PROXY_SUBCMD_CONNECT 0x01
#define TCP_PROXY_SUBCMD_CONNECTED 0x02
#define TCP_PROXY_SUBCMD_DATA 0x03
#define TCP_PROXY_SUBCMD_CLOSE 0x04
#define TCP_PROXY_SUBCMD_ERROR 0x05
#define TCP_PROXY_CONNECTED_OK 0
#define TCP_PROXY_CONNECTED_REFUSED 1
#define TCP_PROXY_HDR_SIZE 6 // svc_id(1)+subcmd(1)+stream_id(4)
#define TCP_PROXY_CONNECT_HDR_SIZE 12 // HDR_SIZE + dest_ip(4)+dest_port(2)
#define TCP_PROXY_CONNECTED_HDR_SIZE 9 // HDR_SIZE + local_port(2)+status(1)
struct remote_proxy_conn {
struct remote_proxy_conn* next;
struct remote_proxy_ctx* ctx;
uint32_t stream_id;
uint64_t peer_node_id;
socket_t sock;
void* read_id;
struct UASYNC* ua;
uint8_t dest_ip[4];
uint16_t dest_port;
uint8_t cli_closed; // client sent CLOSE
uint8_t sock_closed; // socket EOF received
uint8_t error;
uint8_t connected; // connect() completed successfully
uint8_t* pending_buf; // буфер данных пока connect() не завершён
size_t pending_len;
uint8_t* out_buf; // буфер отправки в сокет (неблокирующий send)
size_t out_len;
size_t out_off;
void* out_timer; // таймер повтора send
int out_backoff; // backoff: 10..5000 tb (1ms..500ms)
uint8_t* pause_buf; // буфер при паузе чтения (normalizer переполнен)
size_t pause_len;
void* pause_timer; // таймер паузы чтения
int pause_backoff; // backoff: 50..5000 tb (5ms..500ms)
};
struct remote_proxy_ctx {
int enabled;
int conn_count;
struct remote_proxy_conn* conns;
struct UTUN_INSTANCE* inst;
};
int remote_proxy_init(struct UTUN_INSTANCE* inst);
void remote_proxy_destroy(struct UTUN_INSTANCE* inst);
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id);
void rp_conn_free(struct remote_proxy_conn* rc);
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id);
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
#endif
+39 -15
View File
@@ -15,10 +15,12 @@
#include "etcp_debug.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_node_lmdb.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "route_connectivity.h"
#include "control_server.h"
// ============================================================================
@@ -244,6 +246,16 @@ struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
bgp->instance = instance;
bgp->ping_pending = NULL;
bgp->next_ping_req_id = 1;
bgp->nodeinfo_env = NULL;
bgp->nodeinfo_dbi = 0;
sc_derive_ed25519_pubkey(instance->my_keys.private_key, bgp->ed25519_public_key);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key");
// Создаём/открываем LMDB базу nodeinfo если задан db_path в конфиге
if (instance->config && instance->config->global.db_path[0]) {
route_node_lmdb_init(bgp, instance->config->global.db_path);
}
// Create nodes queue with hash support for fast lookup by node_id
bgp->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, offsetof(struct NODEINFO_Q, node.node_id)-sizeof(struct ll_entry), 8, "bgp_nodes");
@@ -324,6 +336,8 @@ void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
u_free(instance->bgp->local_node);
}
route_node_lmdb_destroy(instance->bgp);
if (instance->bgp->nodes) {
queue_free(instance->bgp->nodes);
}
@@ -356,9 +370,16 @@ void route_bgp_new_conn(struct ETCP_CONN* conn) {
}
struct ROUTE_BGP* bgp = conn->instance->bgp;
struct NODEINFO_Q* peer_nq = nodeinfo_find_by_id(bgp, conn->peer_node_id);
route_bgp_add_to_senders(bgp, conn);
if (peer_nq && peer_nq->alien) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: peer 0x%016llx is alien, skipping route exchange",
(unsigned long long)conn->peer_node_id);
return;
}
// Scan ALL connections and ALL links in instance - start NAT check if not started
struct ETCP_CONN* c = conn->instance->connections;
while (c) {
@@ -429,6 +450,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
if (route_bgp_remove_path(nq, conn) == 1) {
need_withdraw = true;
if (rt) route_delete(rt, nq);
if (conn->instance && conn->instance->control_srv)
control_server_notify_node_removed(conn->instance->control_srv, nq->node.node_id);
nq->dirty = 1;
route_connectivity_cancel_node(conn->instance, nq);
if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; }
@@ -467,19 +490,9 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
* NEW NODEINFO BASED IMPLEMENTATION
* ================================================ */
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) {
return NULL;
}
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key);
return e ? (struct NODEINFO_Q*)e : NULL;
}
struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp) return NULL;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
if (!nq || !nq->paths || !nq->paths->head) return NULL;
struct ll_entry* e = nq->paths->head;
struct NODEINFO_PATH* best = NULL;
@@ -510,8 +523,8 @@ int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t*
path->hop_count = hop_count;
uint64_t* stored = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
memcpy(stored, hop_list, hop_size);
queue_data_put(nq->paths, pe);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Added path for node %016llx via conn %s with %d hops", (unsigned long long)nq->node.node_id, conn->log_name, hop_count);
queue_data_put(nq->paths, pe);
return 0;
}
@@ -650,6 +663,7 @@ static void route_bgp_extract_nat_addr(struct ETCP_CONN* conn, uint32_t* nat_ip,
void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link) {// проверяем тип нат для линка link
if (!bgp || !link || !link->conn || !link->etcp) return;
if (link->nat_type == NAT_TYPE_DIRECT) return;
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return;
// Skip NAT check for private sockets - they are not reachable from outside
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE) {
@@ -728,7 +742,7 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
return -1;
}
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nodeinfo1 = nodeinfo_find_by_id(bgp, node_id);
uint8_t new_ver = ni->ver;
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
@@ -771,6 +785,8 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
nodeinfo1->connectivity.interface_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.nat_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.real_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.ping_req_time = 0;
nodeinfo1->alien = 0;
queue_data_put_with_index(bgp->nodes, &nodeinfo1->ll);
} else {
socks_changed = (nodeinfo1->node.local_v4_sockets != ni->local_v4_sockets) ||
@@ -813,6 +829,12 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
route_insert(bgp->instance->rt, nodeinfo1);
}
if (node_id != bgp->instance->node_id) {
route_node_lmdb_put(bgp, nodeinfo1);
}
if (bgp->instance->control_srv) control_server_notify_node_change(bgp->instance->control_srv, nodeinfo1);
int hop_count=nodeinfo1->node.hop_count;
// рассылаем по узлам которых нет в hop_list этой ноды (loop prevention)
@@ -850,7 +872,7 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
uint64_t node_id = wp->node_id;
uint64_t wd_source = wp->wd_source;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
if (!nq) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
return 0;
@@ -858,6 +880,7 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
int ret=route_bgp_remove_path_by_hop(nq, wd_source);
if (ret>0 || !nq->paths || (nq->paths && queue_entry_count(nq->paths) == 0)) {
if (bgp->instance && bgp->instance->rt) route_delete(bgp->instance->rt, nq);
if (bgp->instance->control_srv) control_server_notify_node_removed(bgp->instance->control_srv, node_id);
nq->dirty = 1;
route_connectivity_cancel_node(bgp->instance, nq);
if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; }
@@ -923,8 +946,8 @@ static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* co
struct ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (item_entry) {
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added to senders_list: %s", conn->log_name);
queue_data_put(bgp->senders_list, item_entry);
}
}
}
@@ -990,6 +1013,7 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
uint8_t verified_type;
if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM;
else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT;
else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT;
else verified_type = NAT_VERIFIED_UNKNOWN;
int data_changed = 0;
+5 -6
View File
@@ -4,6 +4,7 @@
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "../lib/liblmdb/lmdb.h"
#include "route_lib.h"
#include "route_node.h"
#include "secure_channel.h"
@@ -88,6 +89,9 @@ struct ROUTE_BGP {
struct route_ping_pending* ping_pending;
uint32_t next_ping_req_id;
uint8_t allow_nat_check_local; // 1 = разрешить NAT check для локальных подсетей (для тестов)
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // свой Ed25519 pubkey (для подписи сообщений)
MDB_env* nodeinfo_env; // LMDB environment handle
MDB_dbi nodeinfo_dbi; // LMDB database handle for nodeinfo table
};
/**
@@ -151,12 +155,7 @@ void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
*/
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск NODEINFO_Q по node_id через hash в nodes queue.
*
* @return node или NULL
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
int nodeinfo_dyn_size(struct NODEINFO* node);
/**
* @brief Поиск оптимального ETCP соединения для указанного node_id.
+12 -1
View File
@@ -121,6 +121,13 @@ int get_node_v6_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SUBNE
return (int)info->local_v6_subnets;
}
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) return NULL;
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key);
return e ? (struct NODEINFO_Q*)e : NULL;
}
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");
@@ -176,9 +183,12 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|| (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 (bgp->local_node && bgp->instance && bgp->instance->bgp) {
if (memcmp(bgp->local_node->node.ed25519_public_key, bgp->instance->bgp->ed25519_public_key, SC_PUBKEY_SIZE) != 0) changed = 1;
}
}
if (changed) {
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;
@@ -193,6 +203,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
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);
memcpy(bgp->local_node->node.ed25519_public_key, bgp->ed25519_public_key, SC_PUBKEY_SIZE);
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
+15 -1
View File
@@ -33,6 +33,12 @@ struct UTUN_INSTANCE;
#define CONN_PROBE_COUNT 3
#define CONN_PROBE_TIMEOUT_MS 1000
#define CONN_TYPE_NONE 0
#define CONN_TYPE_DIRECT 1
#define CONN_TYPE_REVERSE 2
#define CONN_TYPE_INDIRECT 3
#define CONN_MGR_MAX_INTERMEDIARIES 3
// ---- состояние связности с удалённым узлом (локальное, не передаётся по BGP) ----
struct NODE_CONNECTIVITY {
@@ -49,6 +55,7 @@ struct NODE_CONNECTIVITY {
uint64_t interface_probe_time;
uint64_t nat_probe_time;
uint64_t real_probe_time;
uint64_t ping_req_time; // время последнего полученного ping request от этого узла (0.1ms tb)
};
/**
@@ -57,7 +64,8 @@ struct NODE_CONNECTIVITY {
struct NODEINFO {
uint64_t node_id; // (big-endian)
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t public_key[SC_PUBKEY_SIZE]; // X25519 pubkey
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // Ed25519 pubkey for signature verification
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET_META число метаданных ipv4 сокетов
uint8_t local_v4_addrs; // NODEINFO_IPV4_ADDR число типизированных ipv4 адресов узла (плоский список)
@@ -132,6 +140,10 @@ struct NODEINFO_Q {
struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH
uint8_t dirty;
uint8_t last_ver;
uint8_t alien; // 1 = чужой узел (не запрашиваем роутинг при connect)
uint8_t conn_mgr_type; // CONN_TYPE_* — тип managed-подключения
uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT
uint8_t conn_mgr_intermediariy_count;
struct NODE_CONNECTIVITY connectivity; // состояние связности (локальное)
struct ETCP_SOCKET* best_socket;
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
@@ -173,4 +185,6 @@ int get_node_v6_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV6
*/
int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR **out_addrs);
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id);
#endif // ROUTE_NODE_H
+150
View File
@@ -0,0 +1,150 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include "route_node_lmdb.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define NODEINFO_DB_MAPSIZE (10UL * 1024 * 1024)
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path) {
if (!bgp || !db_path || !db_path[0]) return -1;
int rc = mdb_env_create(&bgp->nodeinfo_env);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_create failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_env_set_mapsize(bgp->nodeinfo_env, NODEINFO_DB_MAPSIZE);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_mapsize failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_env_set_maxdbs(bgp->nodeinfo_env, 2);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_maxdbs failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_env_open(bgp->nodeinfo_env, db_path, 0, 0644);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_open(%s) failed: %s", db_path, mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
MDB_txn* txn = NULL;
rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_begin failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_dbi_open(txn, "nodeinfo", MDB_CREATE, &bgp->nodeinfo_dbi);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_dbi_open(nodeinfo) failed: %s", mdb_strerror(rc));
mdb_txn_abort(txn);
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_commit failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database opened: %s", db_path);
return 0;
}
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp) {
if (!bgp) return;
if (bgp->nodeinfo_env) {
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database closed");
}
}
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq) {
if (!bgp || !bgp->nodeinfo_env || !nq) return -1;
int dyn_size = nodeinfo_dyn_size(&nq->node);
size_t val_size = sizeof(struct NODEINFO) + dyn_size;
MDB_val key, data;
key.mv_size = 8;
key.mv_data = &nq->node.node_id;
data.mv_size = val_size;
data.mv_data = (void*)&nq->node;
MDB_txn* txn = NULL;
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_begin failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_put(txn, bgp->nodeinfo_dbi, &key, &data, 0);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_put node %016llx failed: %s",
(unsigned long long)nq->node.node_id, mdb_strerror(rc));
mdb_txn_abort(txn);
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_commit failed: %s", mdb_strerror(rc));
return -1;
}
return 0;
}
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodeinfo_env) return -1;
MDB_val key;
key.mv_size = 8;
key.mv_data = &node_id;
MDB_txn* txn = NULL;
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_begin failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_del(txn, bgp->nodeinfo_dbi, &key, NULL);
if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_del node %016llx failed: %s",
(unsigned long long)node_id, mdb_strerror(rc));
mdb_txn_abort(txn);
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_commit failed: %s", mdb_strerror(rc));
return -1;
}
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef ROUTE_NODE_LMDB_H
#define ROUTE_NODE_LMDB_H
#include <stdint.h>
#include "route_node.h"
struct ROUTE_BGP;
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path);
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp);
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq);
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id);
#endif
+10 -5
View File
@@ -186,6 +186,10 @@ int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
e->dgram = (uint8_t*)req_pkt;
e->len = pkt_size;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
(unsigned)new_req_id,
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
int ret = etcp_send(to_conn, e);
if (ret != 0) {
u_free(req_pkt);
@@ -208,10 +212,6 @@ int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn,
bgp->ping_pending = pending;
pending->timeout_timer = uasync_set_timeout(bgp->instance->ua, wait_timeout_ms * 10, pending, route_ping_pending_timeout, "route_ping");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
(unsigned)pending->request_id,
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
return 0;
}
@@ -325,6 +325,11 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
ctx->request_id = req_pkt->request_id;
ctx->count_total = req_pkt->count;
ctx->timeout_ms = req_pkt->timeout_ms;
{
struct NODEINFO_Q* req_nq = nodeinfo_find_by_id(bgp, from_conn->peer_node_id);
if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb();
}
if (len >= sizeof(struct BGP_PING_REQUEST)) {
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE);
}
@@ -338,7 +343,7 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
/* Если target не указан — можно разрешить из nodeinfo */
/*
if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) {
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, req_pkt->node_id);
if (nq) {
const struct NODEINFO_IPV4_ADDR* addrs;
int count = get_node_v4_addrs(nq, &addrs);
+4 -3
View File
@@ -147,7 +147,7 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to node %016llx dst=%s",
ip_len, (unsigned long long)nq->node.node_id, ip_to_str(&addr, AF_INET).str);
int send_err = etcp_route_send(instance, nq->node.node_id, entry);
int send_err = etcp_route_send(instance, nq->node.node_id, entry, 1);
if (send_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_route_send failed: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, send_err);
@@ -165,17 +165,18 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)src_node_id);
}
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to TUN", ip_len);
int put_err = queue_data_put(instance->tun->input_queue, entry);
instance->routed_packets++;
if (put_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, put_err);
instance->dropped_packets++;
instance->routed_packets--;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
}
// Callback for packets from ETCP (via etcp_router_bind id=0)
+272 -600
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 566 files, more files were not shown because too many files have changed in this diff. Show more