141 Commits
Author SHA1 Message Date
Evgeny 37417ba49d Fix: my_node_name insert inside [global] section, add button tooltips in channellist 2026-07-14 09:17:01 +03:00
Evgeny cca3511503 fix: replace deprecated SHA256 API with EVP, conditional dl linkage for Windows 2026-07-14 09:07:03 +03:00
Evgeny d70cdbb040 chatgui: preserve active channel selection on reload, members sync, profile settings, member list model 2026-07-14 08:52:10 +03:00
Evgeny c3f946fb63 chat_sync: fix join group stall — remove node_id match in cs_on_conn_up, add CS_MSG_ERROR protocol response 2026-07-13 18:11:18 +03:00
jeka 93de0aa591 agents 2026-07-13 15:46:29 +03:00
Evgeny 0c2eeb8820 chatgui: validate config options on startup, refuse to start on errors 2026-07-13 15:46:20 +03:00
Evgeny 637b21da9e debug: remove console_level/file_level — simplified output routing
Remove secondary level filtering in debug_output(). Now the only filter
is debug_should_output() (per-category + global fallback). Output goes
to file (if file_output set) or console (if console_enabled), with no
additional level check.

Removed:
- debug_set_console_level(), debug_set_file_level()
- console_level, file_level fields from debug_config_t
- debug_set_level() no longer sets console_level
2026-07-13 15:38:08 +03:00
Evgeny c0935aba6c docs: update AGENTS.md and etcp_arch.md 2026-07-13 15:21:30 +03:00
Evgeny 6bf267924d add DEBUG_CATEGORY_CONNECTIVITY for handshake/protocol lifecycle logs
New category (id=25) replaces DEBUG_CATEGORY_DEBUG in chat_sync and
chat_core. This allows enabling only protocol-level logs (SEND/RECV,
timeouts, handshake) without the noise from DEBUG category.

Changes:
- debug_config.h: add CONNECTIVITY=25, COUNT→26
- debug_config.c: add 'connectivity' to g_debug_categories
- chat_sync.c: DEBUG→CONNECTIVITY in all protocol logs
- chat_core.c: DEBUG→CONNECTIVITY in connection/create logs
- utun_node.cpp: remove hardcoded DEBUG=TRACE (now config-driven)
- chatgui.cfg: connectivity=info in debug_categories
2026-07-13 15:14:48 +03:00
Evgeny 5318476cdf chat_sync: add protocol request timeouts and SEND/RECV debug logs
- Add info_req_timer (5s) for CHANNEL_INFO_REQ — reports CONN_MGR_ERR_TIMEOUT
  to GUI on timeout, clears pending invite state
- Add join_timer (8s) for CHANNEL_JOIN → WELCOME — reports timeout on failure
- cs_on_conn_down: cancel all proto timers, reset pending invite state
- cs_send: log every outgoing message (SEND <type> to=<peer> ch=<id> len=<n>)
- Dispatcher: log every incoming message (RECV <type> from=<peer> ch=<id> len=<n>)
- Add cs_msg_name() helper for readable message type names
- pending_invite_node_id/ch_id no longer cleared immediately after send —
  kept until response or timeout, enabling proper error reporting
2026-07-13 15:06:06 +03:00
Evgeny 24294b22f6 fix: use got_initial_pkt instead of new_conn for send_reset decision
new_conn is only true for the first INIT after server restart.
Subsequent INITs from same peer (recovery loop) see new_conn=0
and revert to old logic, sending 0x05 (no reset) and overwriting
the first 0x03 response.

got_initial_pkt stays 0 until client actually sends seq=1, so
server keeps sending 0x03 across all INIT retries until client
resets and starts from seq=1.
2026-07-13 14:32:16 +03:00
Evgeny 43d95016ae fix: send INIT_RESPONSE with reset when server creates new connection after restart
When server restarts, new ETCP_CONN has session_id=0 (calloc).
If client sends INIT_REQUEST_NOINIT (0x04) with session_id=0,
the condition conn->session_id != session_id is false (both 0),
so no reset was sent. Client kept old seq numbers while server
expected seq=1, causing 'Waiting for initial packet' deadlock.

Fix: add || new_conn to the condition — when server created a
fresh connection (was restarted), always send reset to client.
2026-07-13 14:19:27 +03:00
Evgeny 41e0736b75 etcp_connections: force reinit when client sends ETCP_INIT_REQUEST (reset)
Server now checks both code==ETCP_INIT_REQUEST and session_id mismatch
to decide send_reset. Previously only session_id was checked, so if
session matched (both sides had 00000000), reinit was skipped even
when client explicitly requested reset (code 0x02).

Client side already correct: handle_init_response_client:1473 calls
etcp_conn_reinit if pkt_code==ETCP_INIT_RESPONSE (0x03).
2026-07-13 13:29:58 +03:00
Evgeny 1d29cab751 etcp: INFO-level TX DATA log + Initial packet received log
- 1133: DEBUG_DEBUG -> DEBUG_INFO for TX DATA (seq, len, retry, queue lengths)
- 1437: add DEBUG_INFO when initial packet seq=1 accepted

These two INFO lines show the exact order of first sends and receives
to diagnose INIT sequencer sync without guessing.
2026-07-13 13:19:08 +03:00
Evgeny d0f5a2970f etcp_connections: improve INIT diagnostics (demote spam, add type/source/fields)
- 1556: Normal decryption failed -> DEBUG_DEBUG (expected for INIT, no more spam)
- 1581: added 'INIT X25519 OK from %s' with source address
- 1604-1617: INFO log for decrypted packet type (PING/PONG/INIT) with peer_id+src
- 1662-1668: expanded INIT accepted log with all fields:
  peer_id, mtu, link_id, socket_id, type, only_local, src_ip:src_port,
  session_id, actual src (UDP) — shows NAT mismatch
2026-07-13 11:01:50 +03:00
Evgeny 03701f525f conn_mgr: add missing sc_init_ctx before sc_set_peer_public_key
Without sc_init_ctx, crypto_ctx.initialized==0, sc_set_peer_public_key returns
SC_ERR_NOT_INITIALIZED, and all subsequent sc_encrypt (INIT, etc) fail with
'encryption failed' error 2. Fixed in cm_start_phase_direct and
reverse-connect path (both places that create ETCP_CONN for conn_mgr).
2026-07-13 10:03:15 +03:00
Evgeny 89b1a01448 invite+clipboard+conn_mgr: sock_meta for alien nodes, clipboard fix, bg_ping fix
- chat_core_connect_from_invite: add TOPO_SOCKMETA4 (id=0, UNKNOWN, UNKNOWN)
  so cm_has_direct_ip/cm_start_phase_direct can match socket_id=0 addr
- conn_mgr: cm_bg_ping_timer_cb reads real addr from nq->node->v4_addrs
  instead of NULL; added cm_bg_ping_noop_cb per required etcp_send_ping_to_socket
- invitedialog: set both QClipboard::Clipboard and Selection on Linux/X11
2026-07-13 09:37:46 +03:00
Evgeny e3b32cf4d3 conn_mgr: fix all etcp_route_send calls (dgram was NULL) + improve error diagnostics
- conn_mgr.c: 5 call sites used queue_entry_new(data_size) with data[] instead
  of dgram — all etcp_route_send calls silently dropped. Fixed by switching to
  dgram-based allocation (queue_entry_new(0)+u_malloc) matching all other callers.
- DIRCECT_REQ reuses already-allocated pkt as qe->dgram (no extra memcpy).
- etcp_router.c:1078 — 'empty entry' now shows dgram=%p len=%u dst=0x... force=%d
- etcp_connections.c:1139 — 'bad args' now shows all arg pointers to identify NULL
- etcp_connections.c:1145 — 'no socket' now shows addr_family=%d
- conn_mgr.c:633 — 'no candidates' now notes direct+reverse failed
2026-07-13 08:49:20 +03:00
Evgeny 2d93dd66c8 chatgui: fix Windows build - replace netinet/in.h with socket_compat.h 2026-07-13 08:08:27 +03:00
Evgeny 85a91a1320 chatgui: invite link connect flow + GUI_ERROR always-on diagnostics
- debug_ui.h: remove NDEBUG guard so GUI_ERROR works in Release builds
- invite_link.h/cpp: add error field to InviteData with specific failure reason
- joindialog: show exact decode error in QLabel instead of generic message
- chat_sync: implement invite connect flow (CHANNEL_INFO_REQ/RESP, JOIN, WELCOME)
- topo_node_sqlite: node/address table init and lookup
- utun_node: getInviteAddresses fallback from ETCP sockets
- minor fixes in mainwindow, chat_core, gui_bridge
2026-07-13 08:06:19 +03:00
Evgeny 102816d0dc node_config: load() returns true on parse success; main: specific diagnostics per missing field 2026-07-13 07:52:49 +03:00
Evgeny c65940f499 Copy Qt platforms/qwindows.dll plugin alongside executable 2026-07-13 07:44:06 +03:00
Evgeny 79bdafc9a7 Optimize recursive DLL resolution: scan only newly added DLLs 2026-07-13 07:43:25 +03:00
Evgeny cb1021f231 Recursive DLL dependency resolution via objdump for build.sh windeployqt fallback 2026-07-13 07:36:51 +03:00
Evgeny 58503907b5 Use objdump -p for smart DLL dependency discovery as windeployqt fallback 2026-07-13 07:33:49 +03:00
Evgeny 666507d3fe Move Qt DLL deployment from CMake to build.sh (windeployqt + manual fallback) 2026-07-13 07:30:56 +03:00
Evgeny f0f20c158b Simplify windeployqt: use PATH, add diagnostics, --no-compiler-runtime 2026-07-13 07:23:35 +03:00
Evgeny ed5ccd1ffb Add windeployqt post-build step for automatic Qt DLL deployment on Windows 2026-07-13 07:19:37 +03:00
Evgeny 65611790fa Fix enterEvent signature for Qt5/Qt6 compatibility 2026-07-13 07:06:58 +03:00
Evgeny e74240f0dd Add my_ed25519_privkey field to UTUN_INSTANCE struct 2026-07-13 07:01:42 +03:00
Evgeny e438e19f9e Add cross-platform utun_mkdir() wrapper for Windows compatibility 2026-07-13 06:44:18 +03:00
Evgeny de7841f33a chatgui: create group, unified logging, fix uasync_post epoll bug
- Add CreateGroupDialog with group name input
- Generate X25519+Ed25519 keypairs, random uint64 channel_id,
  Ed25519 signature, write to DB via uasync thread
- chat_core_create_channel(): create msg table, topo_node_sqlite_channel_put()
- GUI_EVT_CHANNEL_UPDATED notification to reload channel list
- Per-category debug config: debug_level= + debug_categories= in [gui]
- Unified logging via debug_config: all threads write to same file/console
- debug_enable_console(1) to mirror file output to stderr
- DEBUG_LEVEL_DISABLED=-1: truly disable category, NONE=0 fallback to global
- Fix uasync_post under epoll: process_posted_tasks was never called
- Fix MainWindow constructor parameter ordering (dbPath vs debugFile)
- debug_ui.h: append mode ('a' instead of 'w')
2026-07-12 17:39:20 +03:00
Evgeny a9a6b6bb10 TOPO_GROUP: add group_type support (UTUN/CHAT), mismatch detection, topo_groups_create_group()
- CHAT groups don't send subnets and skip route insert/delete
- Group type mismatch sends ERR_GROUP_MISMATCH to peer
- New topo_groups_create_group() public API for creating typed groups
2026-07-11 22:16:39 +03:00
Evgeny 3f874a80ab TOPO_GROUP: embed struct ll_entry as first member (direct queue embedding, hash index) 2026-07-09 19:50:17 +03:00
Evgeny 1f4749a1b9 Убрал имена функций из DEBUG-сообщений (__FUNCTION__ уже добавляет)
Оставлены метки-подсказки: NODEINFO, ACK_SEND, ETCP_SEND, DUMP,
router, conn_mgr, db_sync, probe, ctrl_out, socks_proxy и т.д.
2026-07-09 19:23:49 +03:00
Evgeny 28e5950d29 TOPO_BGP -> TOPO_GROUP: multi-group support
- Renamed TOPO_BGP to TOPO_GROUP with topo_group_id (64bit)
- Added TOPO_GROUPS container (ll_queue group_list) in UTUN_INSTANCE
- Memory pools moved from TOPO_GROUP to TOPO_GROUPS (instance-level)
- Default group: TOPO_GROUP_UTUN = 0x8000000000000000
- topo_groups_get_default() searches by group_id, not just first entry
- All topo_bgp_* renamed to topo_group_*; function signatures updated
- Files: topo_bgp.h/c -> topo_group.h/c
2026-07-09 18:08:49 +03:00
Evgeny 3c3d9a65d9 refactor: переименование route_node/route_bgp в topo_node/topo_bgp
Файлы: route_node.* route_node_lmdb.* route_bgp.* -> topo_node.* topo_node_lmdb.* topo_bgp.*

Структуры:
- ROUTE_BGP -> TOPO_BGP, ROUTE_BGP_CONN_ITEM -> TOPO_BGP_CONN_ITEM
- NODEINFO -> TOPO_NODE, NODEINFO_Q -> TOPO_NODEQ, NODEINFO_PATH -> TOPO_NODEPATH
- NODEINFO_ROUTES -> TOPO_NODESUBNETS, NODE_CONNECTIVITY -> TOPO_CONNECTIVITY
- wire: NODEINFO_MSG -> TOPOMSG_NODE, NODEINFO_IPV4_* -> TOPOMSG_*4 и т.д.
- memory: NI_IPV4_* -> TOPO_*4, NI_IPV6_* -> TOPO_*6
- ROUTE_SUBCMD_* -> TOPO_SUBCMD_*, ETCP_ID_ROUTE_ENTRY -> ETCP_ID_TOPO_ENTRY

Функции:
- route_bgp_* -> topo_bgp_*
- nodeinfo_* -> topo_node_*
- route_node_* -> topo_node_*
- route_node_lmdb_* -> topo_node_lmdb_*

Смысл: отделение механизма формирования топологии узлов от IP-роутинга.
2026-07-09 17:01:56 +03:00
Evgeny c7ac42eb5f route_node: refactor NODEINFO — separate protocol/memory structs, linked lists with memory_pool
- NODEINFO_MSG (protocol, packed): added flags byte + group_id field
- NODEINFO (memory): ref_count, linked list heads instead of inline arrays
- NODEINFO_ROUTES: separate struct with linked list subnet heads
- NODEINFO_Q: node*, routes*, tranzit_data*, hop_list* as separate mallocs
- 6 memory_pools in ROUTE_BGP for NI_* linked list items
- ni_list_count() universal counter via _ni_head cast
- nodeinfo_serialize/deserialize for protocol <-> memory conversion
- NODEINFO_FLAG_SEND_SUBNETS controls subnet data in protocol
- group_id defaults: utun=NODEINFO_GROUP_UTUN(1) with subnets

Fixes:
- deserialization: data+2 instead of data+sizeof(BGP_NODEINFO_PACKET)
- memset: start after ll_entry to preserve ll.size
- hop_src: subtract hop_count*8 to point at correct dynamic offset
- same-ver branch: remove_path(conn) instead of remove_path_by_hop(peer)
- double-free in route_bgp_remove_conn/process_withdraw
- conn_mgr_add_alien_node: rewritten for new structures
- all test files updated for new API
2026-07-09 16:20:48 +03:00
Evgeny 62954ca0a9 chatgui: replace MsgClient/ChatPropagator/crypto with chat_core, update DB/messaging/layout 2026-07-08 21:15:45 +03:00
Evgeny 1889d23240 metrics: RTT/loss histograms + Ed25519 signing + peer exchange via INIT handshake + file logging to stats/<peer>.dat
- etcp_metrics struct: 12-bucket RTT histogram, 11-bucket loss histogram, 10-min snapshots (>=200 pkts)
- Total counters: packets/bytes sent/rcvd, losses (retrans + probe gap detection)
- CSV append to <stats_dir>/<peer_id>.dat every 10 min
- Ed25519 pubkey exchange in INIT handshake (V3), always present
- Signed metrics exchange via ETCP_SECTION_METRICS (0x0A)
- Received metrics saved as from_<peer_id>.dat with signature hex
- on_metrics_rcvd callback in ETCP_BINDINGS
- stats_dir derived from config file path, mkdir'd at instance init
- Simplified versioning: ETCP_INIT_REQ_SIZE / ETCP_INIT_RESP_SIZE via sizeof
2026-07-07 17:16:45 +03:00
Evgeny f5c93266dc security: hardening flags + control_server fixes (F-001, F-002)
- Add --hardened flag to build.sh with FORTIFY, stack protector, PIE, RELRO
- Add --enable-hardening to configure.ac
- F-001: fix %s -> %.32s to prevent stack leak from non-null-terminated network string
- F-002: add pre-check recv_len >= MAX before buf_space computation to prevent size_t underflow
2026-07-07 07:47:37 +03:00
jeka 861d81815f gui update db and connection 2026-07-06 21:57:52 +03:00
jeka 9dafb4c31e gui update db and connection 2026-07-06 21:52:46 +03:00
Evgeny 19a4505a03 chat_sync: новый протокол синхронизации чата вместо ChatPropagator
- gui_bridge.h/cpp — кросс-поточные асинхронные коллбэки uasync↔GUI
- chat_sync.h/c — протокол синхронизации (INIT_SYNC/RESP, SEND_DATA, PUSH/ACK, SYNC_DONE)
- DbManager полностью переписан: per-channel таблицы msg_<ch> + peers_<ch>,
  4 ключа в channels, signature, protocol/rtt в node_addresses, sync-методы
- ChatPropagator удалён, заменён на chat_sync
- db_sync_stub.c — заглушки для сборки chatgui без LMDB-версии db_sync
- db_schema.md актуализирован под новую схему
2026-07-06 16:25:52 +03:00
Evgeny 46f5ff7eca test_etcp_router: fix flaky keepalive timeout under dummynet congestion
Root cause: 80kbps congestion causes 9+ second backpressure during push
phase. No packets reach server → keepalive timeout (2s default) fires
→ ETCP link down → BGP cleanup → etcp_router_conn_restart()
  - frees send_q (up to 64 = ROUTER_MAX_SEND_Q_PACKETS lost)
  - sends 9-byte restart notification to service handler (conn=NULL)
  - resets seq to 0

Fixes:
1. Set keepalive_timeout=60000, keepalive_adaptive=0 in test configs
   to prevent spurious timeouts during intentional congestion testing
2. Handle 9-byte restart notification in srv_handler:
   when conn==NULL && len==9 → reset g_expected_seq=0 gracefully
2026-07-06 12:28:54 +03:00
Evgeny 7628f77b93 chatgui: integrate UtunNode + NodeConfig, replace MsgClient
- Add NodeConfig: simplified INI config manager for embedded uTun node
  - X25519 key generation via OpenSSL EVP
  - Random node_id generation
  - addServer/removeServer, addClient/removeClient API
  - Auto-creates config with default server on first run
- Integrate UtunNode into MainWindow:
  - Creates NodeConfig in ~/.config/chatgui/node.conf
  - Starts embedded uTun node (no TUN, no msg_transport)
  - In separate thread with uasync_poll event loop
- Replace MsgClient (TCP IPC) with UtunNode (direct calls):
  - ChatPropagator now uses UtunNode for send/recv
  - send(uint64_t, QByteArray) replaces sendTo
  - messageReceived signal replaces received signal
- MainWindow cleanup: remove MsgClient, add UtunNode+NodeConfig
2026-07-06 11:59:01 +03:00
Evgeny 118ab04788 chatgui: add UtunNode C++ wrapper for embedded uTun node
- UtunNode runs uTun instance in a dedicated std::thread
- no TUN, no msg_transport — direct etcp_route_send/recv calls
- Thread-safe cross-thread communication via Qt signals
- C callback (etcp_recv_fn) stores UtunNode* via thread_local
- etcp_router_bind on service ID 0x11 for chat messages
- uasync_poll(100ms) loop runs until stop() called
2026-07-06 11:51:41 +03:00
Evgeny bfb83f7459 chatgui: add CMake build for libutun (C library linked into GUI)
- Add tools/chatgui/libutun/CMakeLists.txt — builds libuasync (from lib/*.c)
  and libutun (from src/*.c except utun.c) as static libraries
- All C code compiled as C99, linked into chatgui C++ binary
- Include paths and defs match autotools build
- chatgui CMakeLists.txt: add_subdirectory(libutun), link utun library
- Fix zxing-cpp docs/ directory issue
2026-07-06 11:48:56 +03:00
Evgeny 797c0736a7 headers: add extern "C" guards for C++ interop (chatgui)
Added #ifdef __cplusplus / extern "C" { ... } / #endif to ~65 headers
in src/ and lib/, enabling the C library to be linked into C++ code (chatgui).
Fixed packet_dump.h (added missing header guard) and etcp_bbr.h
(#pragma once guard).
2026-07-06 11:44:29 +03:00
Evgeny 8756f561be build: extract libutun.a from src/ + add db_sync module + etcp API, BBR improvements
- Add libutun.a (noinst) containing all src/*.c except utun.c
- utun binary now links libutun.a + libuasync.a
- tests/Makefile.am simplified: all tests link libutun.a instead of
  manual .o file lists (removed ~70 lines of boilerplate)
- Add db_sync module: distributed content-addressed DB with LMDB
  backend, peer-to-peer sync via etcp_router (svc 0x20)
- etcp API: etcp_send_in_order(), etcp_clear_inflight_queues(),
  verify_packet_signature(), BBR getters
- BBR: adaptive threshold (1/4 of pipe-min instead of hardcoded)
- route_bgp/etcp_connect: STCP transport support in NODEINFO
- conn_mgr: NODEINFO_ADDRS doc for nat_type/transport/proto fields
- Cleanup: removed AUDIT_FINDINGS.txt, filelist.txt, nat.txt
2026-07-06 11:42:13 +03:00
jeka 7139969acd fix: Windows/MSYS2 build compatibility
- proxy/icmp_proxy.c: define struct icmp/ip + ICMP constants for _WIN32
- lwip_tcp_priv.h: use #include <winsock2.h> instead of #define htonl/htons
- lwip_tcp.h/c: rename poll field to pollfn (avoid WSAPoll macro conflict)
- tests: guard Unix-only headers with #ifndef _WIN32, skip fork/socketpair tests
2026-07-05 20:57:44 +03:00
Evgeny 59d307f98b conn_mgr.h: document in ll_queue style; config_parser/utun_instance updates 2026-07-05 19:08:13 +03:00
Evgeny 90ad64a106 stcp: add protocol field to NODEINFO addr structs, TCP support in etcp_connect, stcp traffic test
- route_node.h: add NODE_PROTO_UDP/NODE_PROTO_TCP constants, protocol field in NODEINFO_IPV4_ADDR/NODEINFO_IPV6_ADDR
- route_node.c: fill protocol for UDP sockets + TCP servers from config, update dumps
- etcp_connect.c: create stcp_link for TCP-capable addresses, ready/close callbacks, cleanup
- etcp_connections.c: store stcp_server_listen result in instance->stcp_server, fix socket init checks for TCP-only configs
- utun_instance.c: use stcp_link_server_destroy (high-level API) instead of stcp_server_destroy
- tests/test_stcp_traffic.c: 2 nodes via etcp_connect+TCP, 1MB bidirectional traffic (10-1800 byte pkts)
2026-07-05 14:49:35 +03:00
Evgeny 4914348557 etcp_router: транзитные очереди per (src,dst) с backpressure + унификация etcp_send
- TRANSIT_QUEUE: отдельная очередь на каждую пару src+dst узлов
- ll_queue с хеш-индексом для быстрого поиска transit-очередей
- backpressure через waiter на send_input_q (threshold=0, один пакет за вызов, round-robin)
- Очередь создаётся при первом пакете который не получается отправить напрямую, удаляется при опустошении
- Унифицирован etcp_send: единый путь queue_data_put(send_input_q) для UDP (normalizer->input) и STCP (tx_queue)
- Убран STCP-ветвления из router_forward_transit/drain_cb
- Исправлено перекрытие ll.data и tq->q в TRANSIT_QUEUE (добавлены явные поля src_node_id/dst_node_id)
- Фикс лика dgram в tx_queue_cb/client_tx_queue_cb (добавлен queue_dgram_free)
- transit_queues живут внутри ETCP_CONN, инициализируются лениво, очищаются в etcp_connection_close (до pn_deinit) и stcp_link_close
2026-07-05 12:57:41 +03:00
Evgeny 55fa5c7759 fix: add retry_timer fallback in tcp_proxy_server for backpressure stall
waiter-based backpressure (commit ba14e31e) could starve HTTPS CONNECT
worker under concurrent 4-worker load — send_q never drained below threshold
fast enough for the waiter to fire before curl timed out (exit=28).

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

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

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

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

test_socks_http_proxy: +3-й worker (http_proxy через -x без --proxytunnel),
+проверки POST (echo), HEAD (200 OK), OPTIONS (Allow). Все 78 проверок — PASS.
2026-07-02 22:04:37 +03:00
Evgeny 754c08e9c2 etcpmon: widen sockets list by ~100px (15 chars), shift action section 2026-07-02 21:26:21 +03:00
Evgeny 71ccccc2d5 etcpmon: widen sockets list +15px, shift action section right 2026-07-02 21:20:37 +03:00
Evgeny c6d1ebeeaf debug: log etcpmon socket_list NAT values in send_socket_list 2026-07-02 20:40:08 +03:00
Evgeny eea0ed0a48 debug: add NAT category logging to self-detect, init_resp nat update, and NAT_INFO receive paths 2026-07-02 20:31:32 +03:00
Evgeny 0738dcfd5a fix: self-detection uses interface_addr instead of local_addr (works with 0.0.0.0 bind); don't overwrite nat_addr in handle_init_response_client if NAT already verified 2026-07-02 20:24:32 +03:00
Evgeny 4862c30261 add CFG_SERVER_TYPE_LOCAL (type=local) and UNKNOWN semantics: PRIVATE/LOCAL hidden from nodeinfo, PRIVATE blocks incoming INIT, LOCAL accepts; UNKNOWN treated as PUBLIC; self-detection of PUBLIC IP on incoming INIT; filter LOCAL from NAT checks and connectivity 2026-07-02 19:30:08 +03:00
Evgeny 3c5d82a6ee add cross-build-win.sh for Windows cross-compilation; gitignore build-win/ build artifacts only 2026-07-02 18:59:20 +03:00
Evgeny 389ef00da6 refactor: split socket meta 'type' into config_type and nat_type — separate config (PUBLIC/NAT/PRIVATE) from detected NAT (EIM/STRICT/DIRECT) across NODEINFO, conn_mgr, route_bgp, route_node, etcpmon; win32 compat fixes (memmem, SHUT_WR, tun test_mode, tcp_proxy guards) 2026-07-02 18:58:34 +03:00
Evgeny 487cb8718e fix: preserve newlines in config_updater insert/update — no extra newlines on key replacement, add leading nl when inserting at end without trailing nl 2026-07-02 17:37:17 +03:00
Evgeny 42d96f118d fix: swap queue_entry_free/queue_dgram_free order in 27 places — prevents UAF when dgram_free accesses freed entry
uasync: replace raw socket_node* handle with packed index — fixes UAF after socket_array realloc
tcp_io: defer u_free in tcp_conn_destroy via uasync_call_soon — prevents callback-chain UAF
tcp_io: guard read_cb/write_cb with NULL queue checks after deferred destroy
tcp_io: save read_queue to local before queue_data_put + NULL guard after
route_connectivity: linked-list probe_ctx — cancel all parallel probes before nq free
2026-07-02 17:14:38 +03:00
Evgeny 8962f58ea4 uasync: replace raw socket_node pointer with index-based handle — fix UAF after realloc 2026-07-02 17:08:24 +03:00
Evgeny 1f541d6432 tcp_io: save read_queue to local var before queue_data_put, add NULL guard after 2026-07-02 16:38:31 +03:00
Evgeny e66156eb49 tcp_io: deferred free in tcp_conn_destroy to prevent UAF in callback chain 2026-07-02 16:26:43 +03:00
Evgeny 2cd38988e6 AGENTS: add ASAN build instructions 2026-07-02 15:49:10 +03:00
Evgeny d58b532741 gitignore: add utun.pid 2026-07-02 15:48:05 +03:00
Evgeny 4716e54069 build: add --asan flag to build.sh, tcp_io: null sock/queues in destroy + guard read_cb/write_cb 2026-07-02 15:47:54 +03:00
Evgeny aa73e9d8cb route_connectivity: fix UAF in conn_probe_finish — break ctx->nq on cancel_node, always free ctx in finish 2026-07-02 15:02:11 +03:00
Evgeny 993c265125 control_server: add idle timeout (30s) on client socket + large output queue warning 2026-07-02 14:17:42 +03:00
Evgeny 5ca0bbf9e7 tcp_io: place write_monitor=0 before queue_resume_callback to prevent busy-loop when waiter_defer=1 2026-07-02 13:50:43 +03:00
Evgeny 0ef7ee597c tcp_io: fix write_cb busy-loop with deferred callback — turn off write_monitor after flush when waiter_defer=1 2026-07-02 13:43:19 +03:00
Evgeny 7d460ca156 fix: ASAN-detected UAFs and buffer overflows across 6 modules
- etcp_connections: fix link leak & UAF in etcp_socket_remove — remove_link inside etcp_link_close shifts array, skip NULL set and i++
- stcp_server: fix UAF in stcp_conn_process_recv — defer free via uasync_call_soon after stcp_conn_do_close
- etcp: save pkt_len before queue_data_put to output_queue (callback may free entry synchronously)
- socks_proxy: add pool bounds check before memcpy, UINT16_MAX truncation guard, freed flag
- tcp_proxy_server: reorder cleanup (cancel waiters before tcp_conn_destroy), UINT16_MAX guard
- test_route6_lib: increase nodes[] array to STRESS_NODES + STRESS_OPS
2026-07-02 13:35:22 +03:00
Evgeny b738b2ff1b etcp_router: add u_check guards at send_q/recv_q entry points to detect memory corruption 2026-07-02 12:57:21 +03:00
Evgeny cc5289e37c etcp_router: don't reset start_sent in reactive restart — prevents START-flag cascade on multi-hop restart detection 2026-07-02 12:28:31 +03:00
Evgeny 0c5f25c96d etcp_router: fix restart cascade — sync peer_sess_id on auto-create, remove sess_id increment on reactive restart 2026-07-02 12:23:12 +03:00
Evgeny b53758bdbe etcp_router: cancel timers in conn_restart, reset last_ack_sent_tb — fix use-after-free SIGSEGV 2026-07-02 11:34:57 +03:00
Evgeny f5b1c3e789 etcp_router: fix OOB drops after restart — add peer_sync_done to sync rx_seq with first incoming seq 2026-07-02 11:12:14 +03:00
Evgeny 0edd21e335 etcpmon: lift Router to y=1181 (tight to Queues), shrink window to 1400 2026-07-02 10:33:41 +03:00
Evgeny e0c38676e7 etcpmon: compress Queues h 300->280, lift Router y 1260->1240 2026-07-02 10:31:44 +03:00
Evgeny fefd2bcc52 etcpmon: fix layout — restore Debug Levels y=485, lift Links to y=710, compress Queues h=300 y=900 2026-07-02 10:29:58 +03:00
Evgeny 8427e18c98 control_server: fix infinite loop in router_conns aggregation — use read-only head->next traversal instead of get/put 2026-07-02 10:23:06 +03:00
Evgeny 7329fa65f1 etcp_router: add congestion counters, fix tx_seq rollback on etcp_send failure, add router metrics to etcpmon GUI
- etcp_router.h: add 9 per-conn counters (pkts_sent/err/rcvd, ack_sent/recv/stale, dup/oob/sign_fail)
- etcp_router.c: increment counters at 13 event points; fix tx_seq rollback on etcp_send failure (was causing phantom inflight=256 deadlock); add DEBUG_ERROR on all 5 ignored etcp_send returns; fix router_ack_do_send to update timestamps only on success
- etcpmon_protocol.h: add etcpmon_router_metrics (14 aggregated fields)
- control_server.c: iterate router_conns to fill aggregated router metrics
- etcpmon_gui: new 'Router Congestion' block with queue depths and all counters, Debug Levels shifted down, window 1300->1440
- test_etcp_router_unit: fix test_ack_timer for correct no-BGP-route behavior
2026-07-02 09:49:10 +03:00
Evgeny de8cce521a all 51 tests pass: fd=0 protection + clean socks_http_proxy test
- socket_compat: never close fd=0
- socks_http_proxy: rewritten cleanly, 2 workers, 25 reqs each, up to 100KB
- uasync_socket_race: dup2-based fd=0 reservation
- u_async: removed DIAG spam
- tcp_io: removed debug fprintf

Result: 51/51 tests pass.
2026-07-02 00:58:47 +03:00
Evgeny f06ebd2ab4 fix: protect fd 0 from close in socket_close_wrapper + dup2 reserve in tests
- socket_close_wrapper: never close sock==0 (prevents fd 0 reuse cycle)
- test_uasync_socket_race: dup2-based fd 0 reservation, pass 800/800
- tcp_io: SOCKET_INVALID before on_error (previous commit)
- Plus earlier fixes: uasync gen counter, ll_queue call_soon_id, pkt_normalizer

Result: test_uasync_socket_race PASSES, 50/51 tests pass
2026-07-02 00:50:36 +03:00
Evgeny a97a38d867 fix: set tc->sock=SOCKET_INVALID before on_error in handle_error
В одном epoll_wait batch три события для fd: EPOLLERR (освобождает tc),
EPOLLIN (read_cb), EPOLLOUT (write_cb). read_cb/write_cb guard проверяет
tc->sock==SOCKET_INVALID, но sock обнулялся ПОСЛЕ on_error.
Перенос sock=SOCKET_INVALID до on_error защищает от use-after-free.

Доказано: без фикса segfault на tcp_io.c:374 (write_cb → tc->connected).
С фиксом тест работает без крашей.
2026-07-02 00:43:37 +03:00
Evgeny aff3b2d00f fix: always remove socket from epoll in tcp_conn_handle_error
tcp_conn_handle_error гвардил uasync_remove_socket_t по tc->socket_id,
пропуская повторный вызов. В одном epoll_wait batch два события
(EPOLLERR + EPOLLOUT) на один fd: первое делает remove (socket_id=NULL),
второе проходит guard и dispatch'ит write_cb на освобождённом tc.

Фикс: всегда вызывать uasync_remove_socket_t + epoll_ctl(DEL),
независимо от socket_id.
2026-07-02 00:16:05 +03:00
Evgeny ccdbab95a8 fix: use-after-free in pkt_normalizer + ll_queue waiter cancel + uasync gen counter for epoll fd-reuse
- pkt_normalizer: save recvpart->len before queue_data_put (may free synchronously)
- ll_queue: save uasync_call_soon handle in queue_waiter_handle, cancel on free
- uasync: generation counter in epoll data.u64 to skip stale events after fd reuse
- uasync: keep fd_to_index mapping after socket_array_remove for inactive nodes
- test: test_uasync_socket_race — 4 children × 200 iterations, currently flaky
2026-07-01 23:51:31 +03:00
Evgeny a01e2f0899 tun_enabled config + socks/http stress test (50 reqs, 2 workers)
- config_parser: tun_enabled в [global] (по умолчанию yes), не дефолтить
  tun_ifname если TUN не нужен
- utun_instance: уважать tun_enabled, чистые дефолты tcp_proxy TUN
- socks_proxy: фикс дэдлока — reply сразу после CONNECT, без ожидания DATA
- utun.conf.sample: документирован tun_enabled, tun_ifname
- test_socks_http_proxy: 50 запросов (SOCKS+HTTP), 2 воркера параллельно,
  файлы 1k..100k, сверка через cmp, python http.server + curl
2026-07-01 22:14:37 +03:00
Evgeny dfdec06a17 build.sh: add missing ./configure step after autoreconf 2026-07-01 21:19:21 +03:00
Evgeny 393a641e18 socks/http proxy client: SOCKS5 + HTTP CONNECT через ETCP
Добавлен модуль socks_proxy.c/h — SOCKS5 и HTTP CONNECT proxy на клиенте.
Использует tcp_io для локальных TCP соединений, туннелит трафик через
существующий ETCP-протокол (ETCP_ID_TCP_PROXY/ETCP_ID_TCP_PROXY_CLIENT).

Конфиг [tcp_proxy_client]:
- socks_enabled/socks_addr — включить SOCKS5 сервер на указанном IP:PORT
- http_proxy_enabled/http_proxy_addr — включить HTTP CONNECT сервер
- Можно включать TUN, SOCKS и HTTP прокси одновременно или раздельно

Весь трефик идёт через exit node (tcp_proxy_server), менять exit не пришлось.
2026-07-01 20:33:11 +03:00
Evgeny 53d41d5c62 fix: use-after-free in route table — add route_delete before u_free in route_bgp_update_my_nodeinfo and route_bgp_process_nodeinfo, relax routes_overlap to allow hierarchical routes
Root cause: when bgp->local_node was reallocated (changed=1), old route
entries remained with stale v_node_info pointing to freed memory. The freed
memory got reused by a BGP peer's NODEINFO_Q, causing route_lookup to return
wrong node_id (b3b2b1b0afaeadac instead of local), which then failed at
etcp_route_send with 'no BGP route' and dropped packets to 10.23.2.1.

Changes:
- route_node.c: route_delete(old) before u_free + route_insert(new) after alloc
- utun_instance.c: remove duplicate route_insert (now atomic inside function)
- route_bgp.c: route_delete before freeing old peer NODEINFO_Q
- route_lib.c: routes_overlap now rejects only exact prefix+network duplicates
- tests/Makefile.am: add route_lib.o to test_route6_lib
2026-07-01 18:53:23 +03:00
Evgeny c60ba6eaec tun: tun_init_nat always uses TUN_MTU_DEFAULT (1500), ignore ETCP mtu from config 2026-07-01 17:57:36 +03:00
Evgeny c5123e4baf bgp: prevent sending NODEINFO to self + own NODEINFO early return
- route_bgp_process_nodeinfo: skip own node_id early, don't add to bgp->nodes
- broadcast loop: skip sending NODEINFO to connection where peer == node_id
- route_bgp_should_send_to: don't send node's NODEINFO to itself
- tests: lower debug level to ERROR, fix keepalive adaptive config
2026-07-01 17:49:27 +03:00
Evgeny 928c2fe057 tun: always use TUN_MTU_DEFAULT (1500) for TUN interface, ignore ETCP mtu from config 2026-07-01 17:35:53 +03:00
Evgeny b5b6875bc5 bgp: skip own NODEINFO in route_bgp_process_nodeinfo
When own NODEINFO is reflected back from a peer, do not add it
to bgp->nodes. Only update bgp->local_node which is sent separately.
This prevents route duplication and the 'local node in learned list'
inconsistency.
2026-07-01 14:48:38 +03:00
Evgeny 7c953c1929 bgp: add test_bgp_triangle + keepalive_adaptive config option
- keepalive_adaptive=0 disables adaptive keepalive period growth,
  keeping it fixed at 200ms for faster timeout detection in tests
- test_bgp_triangle: 5-node triangle topology test (6 phases):
  validates alternative path recording (fix#1), cascading node
  removal on full isolation, and full recovery
- config generation via utun_instance_create_from_str() with
  pre-generated keys — no temp files needed
2026-07-01 14:22:59 +03:00
Evgeny 1c09ad2e5a bgp: fix node removal when alternative paths exist
- route_bgp_process_nodeinfo: record alternative paths even when NODEINFO
  version hasn't changed (fixes nodes disappearing on link down despite
  having other active links)
- route_bgp_process_withdraw: only remove node when all paths gone (ret==1)
- route_bgp_remove_conn: broadcast withdraw for each removed node with
  correct wd_source
2026-07-01 13:37:35 +03:00
jjj 3284dffaaf 1 2026-07-01 13:02:32 +03:00
Evgeny c893285d71 action popup: use SW_RESTORE + SetForegroundWindow to bring hidden window to front 2026-07-01 12:46:19 +03:00
Evgeny 75028f278a action popup: re-show window on every action result if it was hidden 2026-07-01 12:42:30 +03:00
Evgeny 7fc1b5419f action popup: use ClearType Consolas 10pt font instead of ANSI_FIXED_FONT 2026-07-01 12:27:32 +03:00
Evgeny dee9889423 action popup: convert \n -> \r\n for Windows EDIT control line breaks 2026-07-01 12:24:48 +03:00
Evgeny 35871891db action nodes: send BGP node dump as text response to GUI, display in popup
- route_node_format_all(): format all BGP nodes to snprintf buffer
- control_server: 'nodes' action sends ETCPMON_RSP_ACTION_RESULT (0x89) with text
- etcpmon_protocol.h: new response type + struct, increase MAX_MSG_SIZE to 32K
- etcpmon_client: on_action_result callback, send_action via send_request with seq_id
- etcpmon_gui: ETCPMonActionResult popup window class with EDIT multiline,
  on_action_result creates popup (same size as main) or appends text + scrolls
2026-07-01 12:19:50 +03:00
Evgeny 373e4b33c7 route_node_dump_all: dump all BGP nodes to log via debug INFO
Add route_node_dump_all() in route_node.c that walks bgp->nodes list
and outputs per-node info: node_id, name, version, pubkeys, IPv4/IPv6
addrs/sockets/subnets, paths with hop list, connectivity state, conn_mgr.
Wire 'nodes' action in control_server to call this function.
2026-06-30 23:33:45 +03:00
Evgeny 090e2f28e4 1 2026-06-30 19:18:03 +03:00
Evgeny a2c7f1e6cb fix etcp: infinite loop in feed_dgram_to_asm due to uint16_t overflow in new_cap doubling 2026-06-30 16:21:43 +03:00
Evgeny 21da640ebe route_bgp: find_conn_for_node prefers live links_up path, fallback to best any 2026-06-30 14:59:26 +03:00
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
627 changed files with 389513 additions and 6548 deletions

No files matched your search

+7 -1
View File
@@ -1,5 +1,7 @@
# Build directory (out-of-tree build)
/build/
build-win/
tools/chatgui/build/
# Build outputs
*.o
@@ -59,7 +61,7 @@ Makefile.in
.cache/
# Debug outputs
core
/core
vgcore.*
callgrind.out.*
@@ -96,6 +98,9 @@ utun.conf
tools/bping/bping
tools/proxy/udp_proxy
# Reference projects (not part of utun)
tools/tdesktop-dev/
# Debug/temp artifacts
*.bak
*.debug
@@ -109,3 +114,4 @@ build_win.log
/wintun.dll
src/lwip_orig/
lib/timeout/
utun.pid
+263 -18
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
@@ -56,6 +56,16 @@ cd tests && make # Build only tests
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
```
### ASAN Build (AddressSanitizer, Linux)
```bash
./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible)
```
Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown):
```bash
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
```
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`.
## Test Commands
### Run All Tests
@@ -142,14 +152,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
@@ -184,11 +194,12 @@ sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
### Debug Levels (по возрастанию)
`none < error < warn < info < debug < trace`
### Debug Categories (21 категория)
### Debug Categories (25 категорий)
```
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20,
KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24
```
### Настройка отладки
@@ -205,12 +216,14 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
### Directory Structure
```
├── lib/ # Core libraries (13 .c + 14 .h)
├── src/ # Main source code (28 .c + 23 .h)
├── tests/ # 31+ test programs
├── lib/ # Core libraries (17 .c + 17 .h)
├── src/ # Main source code (42 .c + 44 .h root + 17 в поддиректориях)
├── tests/ # 65 .c файлов, 55 в check_PROGRAMS
├── 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)
@@ -233,20 +246,37 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
- `etcp_debug.c/h` - ETCP packet dump/formatting
- `etcp_router.c/h` - ETCP router/multiplexer (мультиплексирование каналов)
- `etcp_connect.c/h` - ETCP outbound connections
- `etcp_dump.c/h` - ETCP дамп/декодирование пакетов
- `etcp_bbr.c/h` - BBR congestion control for ETCP
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
- `packet_dump.c/h` - Packet hex dump utility
- `firewall.c/h` - Firewall rules (per-interface filtering)
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
**Routing (src/)**
- `routing.c/h` - Routing table management (local routes)
- `route_lib.c/h` - Routing library utilities
- `route_bgp.c/h` - BGP-style route exchange between peers
**BBR (src/BBR/)**
- `bbr_v3.c` - BBR congestion control algorithm v3
**STCP (src/)**
- `stcp.c/h` - STCP протокол (Secure TCP)
- `stcp_server.c/h` - STCP сервер
- `stcp_client.c/h` - STCP клиент
- `stcp_link.c/h` - STCP линк-уровень
**Топология / Маршрутизация (src/)**
- `routing.c/h` - Таблица маршрутов (локальные)
- `route_lib.c/h` - Библиотека маршрутизации
- `route6_lib.c/h` - IPv6 маршрутизация
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
- `route_node.c/h` - Route node (peer) management
- `route_connectivity.c/h` - Проверка связности маршрутов
- `topo_group.c/h` - BGP-подобный обмен маршрутами между узлами
- `topo_node.c/h` - Управление узлами (peer management)
- `topo_node_lmdb.c/h` - LMDB-персистентность узлов
- `conn_mgr.c/h` - Менеджер соединений
**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/)**
@@ -260,6 +290,23 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
**Control (src/)**
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
**Транспорт сообщений (src/)**
- `msg_transport.c/h` - Транспорт сообщений (чат, команды)
- `db_sync.c/h` - Синхронизация БД между узлами
**Прокси (src/proxy/)**
- `socks_proxy.c/h` - SOCKS5 прокси
- `udp_proxy.c/h` - UDP прокси
- `icmp_proxy.c/h` - ICMP прокси
- `tcp_proxy_server.c/h` - TCP прокси (серверная сторона)
- `tcp_proxy_client.c/h` - TCP прокси (клиентская сторона)
**lwIP TCP (src/lwip_tcp/)**
- `lwip_tcp.c/h` - lwIP TCP стек
- `lwip_tcp_out.c` / `lwip_tcp_in.c` - Исходящий/входящий трафик
- `lwip_pbuf.c/h` - Пакетные буферы lwIP
- `lwip_tcp_priv.h`, `lwip_tcp_opts.h` - Внутренние настройки lwIP
**Libraries (lib/)**
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
@@ -274,6 +321,9 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- `myip.c` - My IP helper (no header)
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
- `radix.c/h` - Radix tree for IP routing lookups
- `tcp_io.c/h` - TCP I/O abstraction (Windows IOCP, Linux epoll)
- `wintun.h` - Wintun API header for Windows TUN driver
**Memory Pools in UTUN_INSTANCE:**
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
@@ -285,7 +335,7 @@ 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
@@ -296,7 +346,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
### Запись в очередь
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
- Порог задаётся через `queue_set_threshold(q, max_packets, max_bytes)`.
- Используй `queue_waiter_wait` для ожидания освобождения очереди до заданного порога.
### Чтение из очереди
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
@@ -321,6 +372,40 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
Важное правило отладки: вся отладка сводится к тому чтобы сделать удобные логи которые наглядно показывают поведение и проблемы. без лишнего мусора, компактно и по существу. очень желательно с деталями которые сильно повышают качество отладки.
при отладке нельзя: гадать и долго пытаться разбирать код пытаяясь найти причину. гораздо надёжнее с помощью логов понято точное поведение.
Если логов много - записывай в файл и потом анализируй.
Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали.
Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены.
сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel)
Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи
И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи.
Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто.
Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить.
можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE)
Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке.
Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл.
Рассуждение должно быть примерно таким:
- данные повреждаются при отправке.
- где и почему непонятно. надо продумать ключевые точки где получим максимум полезной информации и не было большого объёма вывода лога.
- где лучше? каждый пакет логировать - много, но будет предельно точная картина
- Еще хорошо бы время - так мы заодно сможем найти проблемы с производительностью, найти проблемные места застревания кода.
- Но это большой объём. Можно ли без него? можно но это будет малоэффективно и хороших альтернатив пока не видно.
- Значит логируем весь трафик, а заодно включим полную трассировку функций.
- Запустили. Записали весь процесс в файл. Теперь можно анализировать. Давай сперва посчитаю количество строк с дампом: ... 50000.
- давай сделаю простой скрипт который дамп прочитает и сохранит в файл. И сверю с оригинальным файлом
- Давай заодно возьму произвольный фрагмент лога и изучу еа предмет явных проблем. Особенно инициализацию и освобождение ресурсов.
Сколько времени занимает, нет ли заклиниваний, нет ли ошибок или странностей
### Диагностические сообщения (DEBUG_WARN DEBUG_ERROR)
Сообщения должны говорить что случилось и с какими деталями.
Обязательно выводить подробные собощения об ошибках (даже если нужен небольшой дополнительный код чтобы эти детали сформировать и привести в читаемый вид).
Полезны подробности об инициализации-завершении которые помогают понять внутренние состояния
Диагностические сообщения - важный момент. надо про диагностику помнить.
Для хорошей диагностики надо проанализировать как архитектор - какие есть нюансы в архитектуре и что полезно будет видеть в дебаг выводе чтобы понять полную картину происходящего.
### Прочие правила:
- sed для редактирования исходников - запрещено
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
@@ -337,11 +422,162 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## chatgui (GUI Chat Client)
Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
В chat gui интегрированы библиотеки utun. Чат и библиотеки работают в разных потоках. Поэтому нужно использовать семафоры, сокеты или другие механизмы синхронизации (uasync_post, uasync_memsync, uasync_get_wakeup_fd)
База данных sqlite в chatgui: можно писать (update) из потока utun / instance. можно только читать из gui потока.
### Технологии
- **Язык:** C++20
- **Фреймворк:** Qt 6 (предпочитаемый) или 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 qt6-base-dev
# или qtbase5-dev для Qt 5 fallback
# Сборка
cd tools/chatgui && mkdir -p build && cd build
cmake .. && make -j4
./chatgui
```
### Структура файлов (src/)
| Файл | Назначение |
|------|-----------|
| `main.cpp` | Точка входа, инициализация QApplication |
| `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` | Список аккаунтов/пользователей |
| `joindialog.h/cpp` | Диалог присоединения к группе/каналу |
| `invitedialog.h/cpp` | Диалог приглашения пользователя |
| `invite_link.h/cpp` | Логика invite-ссылок |
| `settingsdialog.h/cpp` | Окно настроек |
| `creategroupdialog.h/cpp` | Диалог создания группы |
| `networksettingspage.h/cpp` | Страница сетевых настроек |
| `qrcode_utils.h/cpp` | Утилиты для QR-кодов |
| `debug_ui.h` | Отладочный UI |
### Другие поддиректории
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, node_addresses, channels, msg_<channel>, peers_<channel>, local_identity, accounts, ui_state)
- `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `chat_core.h/c` (управление), `chat_sync.h/c` (синхронизация), `db_sync_stub.c` (заглушка БД), `node_config.h/cpp`, `config_updater.h/cpp`, `utun_node.h/cpp`, `topo_node_sqlite.h/c`
- `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 архитектура
- `/doc/etcp_config.txt` - Конфигурация ETCP
- `/doc/etcp_router_arch.md` - ETCP роутер архитектура
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
- `/src/route_bgp.txt` - BGP обмен маршрутами (дизайн-документ)
## Runtime
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
@@ -353,7 +589,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
- **Language:** Mix of English (technical) and Russian (business logic)
- **Tags:** Version tags follow vX.Y.Z format
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая). Запрещено откатывать "лишние" изменения сделаные вне сессии (если сильно надо - сделать бэкап). если нет дополнительных указаний - комить всё что изменилось как есть.
## Quick Start for New Features
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
@@ -362,5 +598,14 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
4. Run `make check` after changes
5. Commit with descriptive message in appropriate language
## chatgui: отправка сообщений через xdotool
1. **Запуск:** `setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown` (иначе SIGTERM убьёт процесс при таймауте bash)
2. **Окно:** `0x4600006 "Chat"` (WM_CLASS "chatgui"), 900×600. Не путать с `0x4800001` (10×10, временное)
3. **Фокус:** клик в область InputBar (x=400, y=570 относительно окна) перед вводом
4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return`
5. **Проверка:** `sqlite3 chats.db "SELECT ... FROM msg_ch_GENERAL"` — сообщения в per-channel таблицах
6. **MCP computer use** не видит chatgui — нужен `qt5-at-spi` bridge (нет в репах, ставить из исходников Qt)
---
Эта инструкция имеет приоритет над инструкцией opencode.
-253
View File
@@ -1,253 +0,0 @@
========================================================================
uTun3 AUDIT FINDINGS — Сводный отчёт
========================================================================
Аудит от 14.05.2026 — обход основных модулей
(use-after-free, out-of-bounds, утечки, reentrancy)
========================================================================
HIGH (19 проблем)
========================================================================
1. u_async.c:137-165 use-after-free
Частичный успех u_realloc в socket_array_add_internal: если часть
реаллоков прошла, а часть нет — старые массивы уже освобождены,
новые не выделены, sa->sockets/sa->fd_to_index/sa->index_to_fd
указывают на освобождённую память.
2. u_async.c:842,1167 логическая ошибка
process_posted_tasks никогда не вызывается на Linux.
В epoll-пути: drain_wakeup_pipe не вызывает handle_wakeup/process_posted_tasks.
В poll-пути: wakeup-слот (i==0) обрабатывается с drain_wakeup_pipe и continue,
минуя зарегистрированный коллбэк.
Следствие: uasync_post из другого потока не доставляет задачи — posted_tasks
накапливаются бесконечно.
3. u_async.c:1383,1398,1418 утечка памяти
При сбое создания wakeup (pipe/socket) в uasync_create не освобождаются:
ua->sockets, ua->timeout_heap, ua->timeout_pool, ua->epoll_fd.
4. memory_pool.c:51-69 double-free
Нет детекта double-free. Повторный free создаёт цикл во freelist,
memory_pool_alloc выдаёт один и тот же блок двум потребителям.
5. etcp.c:679-683 утечка памяти
input_queue_cb: при исчерпании inflight_pool вызывается
queue_entry_free(in_pkt) без queue_dgram_free(&in_pkt->ll).
dgram из data_pool утекает.
6. etcp_connections.c:87,876-926 use-after-free
burst_resp_timer не отменяется в etcp_link_close.
Коллбэк burst_resp_timeout_cb срабатывает на освобождённом link.
7. etcp_connections.c:846-854 use-after-free
stats_timer не отменяется при провале insert_link в etcp_link_new.
link_stats_timer_cb срабатывает на освобождённом link.
8. etcp_connections.c:879-886 use-after-free
Ветка conn==NULL в etcp_link_close пропускает отмену ВСЕХ таймеров
(stats_timer, init_timer, shaper_timer, keepalive_timer, burst_resp_timer).
Любой из них сработает на освобождённом link.
9. etcp_connections.c:1388,1531,1561 out-of-bounds read
INIT request парсинг: проверка pkt_len < 12 должна быть pkt_len < 26
(23 байта заголовка). Пакет с длиной 12-25 пройдёт проверку,
но читает за границей расшифрованных данных.
10. pkt_normalizer.c:33 null deref
if (!pn) pn->alloc_errors++ — разыменование NULL после u_calloc.
11. pkt_normalizer.c:300 null deref
pn_buf_renew в цикле without проверки возврата → memcpy(NULL, ...)
при сбое memory_pool_alloc.
12. eim_nat.c:80-93 логическая ошибка
Переполнение uint16_t порта: next_port с 65535→0, затем порты
ниже port_start выделяются (включая порт 0). Соединения нерабочие.
13. eim_nat.c (весь файл) логическая ошибка
Нет механизма истечения/удаления NAT-записей. Таблица навсегда
исчерпывается при долгой работе, eim_nat_egress начинает отказывать.
14. route_bgp.c:749,762-766 утечка памяти
route_bgp_process_nodeinfo: при пересоздании nodeinfo1 старая
очередь paths сохраняется в локальную переменную, но затем
перезаписывается новой queue_new() — утечка.
15. route_bgp.c:904-907 утечка памяти
route_bgp_send_nodeinfo: при провале etcp_send не освобождаются
p (u_malloc) и e (queue_entry_new).
16. route_ping.c:126-134 утечка памяти
route_ping_cancel_for_conn: nat_check_arg (p->arg, u_calloc) не
освобождается. В нормальном и таймаутном путях коллбэк освобождает,
но cancel-путь коллбэк не вызывает.
17. ll_queue.c:321,439 reentrancy
Синхронный вызов коллбэка при первом queue_data_put (count==1)
без отложенного возобновления через uasync → рекурсивный
ack_timeout_check с устаревшим указателем current.
18. secure_channel.c:379,711 потенциальный OOB write
sc_encrypt пишет в caller buffer без параметра capacity.
Контракт API опасен: вызывающий должен сам гарантировать размер буфера.
19. secure_channel.c:432,756 потенциальный OOB write
sc_decrypt — аналогично, нет проверки capacity выходного буфера.
========================================================================
MEDIUM (18 проблем)
========================================================================
20. u_async.c:591-639 use-after-free
Stale handle в uasync_cancel_timeout/uasync_call_soon_cancel.
Нет инвалидации (generation counter), пул может переиспользовать память.
21. memory_pool.c:8,38,61,95 race condition
Глобальная g_total_free изменяется без блокировок, общая для всех пулов.
22. memory_pool.c:51-69 потенциальный OOB write
Нет проверки принадлежности блока пулу. Free в чужой пул вызывает
memset с чужим object_size — частичная очистка или запись за границу.
23. timeout_heap.c:43-45 утечка / несоответствие документации
Документация обещает free() при free_callback==NULL, код этого не делает.
24. etcp.c:718-763 reentrancy
Вложенный ack_timeout_check (через input_send_q_cb→etcp_request_pkt→
wait_ack_cb) обрабатывает не тот entry, т.к. outer loop использует
current, который мог быть перемещён/удалён внутренним вызовом.
25. etcp.c:747,944 утечка / дрейф счётчиков
Непроверенный queue_data_put_with_index: при провале счётчики
(retransmissions_count, inflight_bytes) не откатываются.
26. etcp_connections.c:1731,1763 out-of-bounds read
INIT_RESPONSE парсинг: нужно >=19 байт, проверяется только >=3.
27. etcp_connections.c:748-753 use-after-free + бесконечный цикл
etcp_socket_remove: при link->conn==NULL remove_link пропускается,
num_channels не уменьшается, цикл перечитывает освобождённый conn->links[0].
28. etcp_connections.c:1736-1738 логическая ошибка
link_status перезаписывается значением remote_keepalive, игнорируя
локальный recv_keepalive. Временно помечает link UP даже когда
локальный приём мёртв.
29. pkt_normalizer.c:43 integer overflow/underflow
frag_size = mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE.
При mtu < 158 беззнаковое вычитание даёт ~65379 → огромные буферы.
30. pkt_normalizer.c:240 утечка памяти
При провале queue_data_put фрагмент и dgram не освобождаются.
31. eim_nat.c:97,173 null deref
Нет проверки ctx->initialized/ctx->table в egress/ingress.
32. eim_nat.c:268,293 логическая ошибка
Статические пробросы (port forward) вне port range недоступны.
33. secure_channel.c:66 key hygiene
Нет sc_free_ctx — сессионный ключ не зануляется при уничтожении контекста.
34. secure_channel.c:322,620 key hygiene
ECDH shared secret остаётся на стеке незанулённым после
sc_derive_session_key.
35. secure_channel.c:352,680 nonce entropy
При постоянном провале random_bytes seed остаётся 0 (static init).
Nonce становится полностью предсказуемым (counter+timestamp).
36. route_bgp.c:1122 утечка памяти
route_bgp_send_nat_info: etcp_send без проверки → pkt/e утекают при провале.
37. route_bgp.c:1157 утечка памяти (аналогично #36)
route_bgp_send_nat_check_req: etcp_send без проверки.
38. config_parser.c:1055-1066 логическая ошибка
update_config_keys дописывает [global] секцию в конец файла.
При повторном вызове получается дубликат секции, парсер может сбойнуть.
========================================================================
LOW (13 проблем)
========================================================================
39. u_async.c:1594-1617 утечка (abort)
abort() в destroy до освобождения posted_tasks и мьютекса.
40. u_async.c:1608 race condition
posted_tasks_head читается без posted_lock в destroy.
41. memory_pool.c:47 truncation
size_t → uint32_t в u_calloc_impl (object_size > UINT32_MAX).
42. memory_pool.h:16 signedness
int free_count может обернуться при двойном освобождении.
43. timeout_heap.c:31 dead code
freed_count никогда не обновляется, timeout_heap_get_freed_count не реализован.
44. timeout_heap.c:66-67 integer overflow
capacity*2 и sizeof(TimeoutEntry)*new_cap могут переполниться.
45. ll_queue.c:106-127 dangling pointer
queue_free освобождает hash_table, но не чистит entry->hash_next.
46. pkt_normalizer.c:374 десинхронизация потока
При провале ll_alloc_lldgram 2 байта заголовка уже consumed,
но фрагмент сброшен → потеря данных.
47. eim_nat.c:105,181 unaligned access (ARM)
*(uint16_t*)(ip_data + 6) — невыровненный доступ на ARM.
48. secure_channel.c:108-131 избыточность
CRC32 поверх CCM (аутентифицированное шифрование) избыточен.
49. secure_channel.h:51-52 dead code
Неиспользуемые поля send_nonce[13]/recv_nonce[13].
50. config_parser.c:312 undefined behavior
~0U << (32 - cidr) при cidr==0 — сдвиг на ширину типа.
51. route_ping.c:227 integer overflow
uint16_t avg_rtt = timeout_ms * 10 — переполнение при timeout_ms > 6553.
========================================================================
Файлы без ошибок
========================================================================
- route_node.c — чисто
- tun_if.c — чисто
- etcp_api.c — только порядок queue_entry_free/dgram_free (LOW, см. etcp.c Issue #7 старого аудита)
- etcp_loadbalancer.c — не аудирован (250 строк)
========================================================================
Рекомендуемый порядок исправления
========================================================================
1. КРИТИЧЕСКИ ФУНКЦИОНАЛЬНЫЕ:
u_async.c #2 (posted_tasks не работают на Linux)
u_async.c #3 (утечки при сбое старта)
2. USE-AFTER-FREE:
etcp_connections.c #6,#7,#8,#5 (висячие таймеры)
u_async.c #1 (частичный realloc)
ll_queue.c #17 (reentrancy)
3. УТЕЧКИ ПАМЯТИ:
etcp.c #5 (dgram из data_pool)
route_bgp.c #14,#15,#36,#37
route_ping.c #16
4. OOB / NULL DEREF:
etcp_connections.c #9,#26 (INIT bounds)
pkt_normalizer.c #10,#11 (null deref)
5. ЛОГИКА:
eim_nat.c #12,#13 (порты + expiry)
memory_pool.c #4 (double-free)
config_parser.c #38 (дубликат секции)
-28
View File
@@ -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
+1 -1
View File
@@ -28,4 +28,4 @@ if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
REM Launch MSYS2 UCRT64 shell and run build script
echo Starting MSYS2 UCRT64 environment...
rem call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -c "bash build.sh > build_win.log 2>&1"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash build.sh 2>&1 | tee build_win.log"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh 2>&1 | tee build_win.log"
+47 -2
View File
@@ -5,6 +5,8 @@
# -h, --help Show this help
# --clean Clean before build
# --full Full rebuild (autoreconf + configure + make)
# --asan Build with AddressSanitizer (debug)
# --hardened Build with compiler security hardening flags
# -j[N] Build with N parallel jobs (default: 4)
set -e # Exit on error
@@ -21,8 +23,11 @@ fi
# Parse arguments
CLEAN=0
FULL_REBUILD=0
USE_ASAN=0
USE_HARDENED=0
JOBS="-j4"
MAKE_ARGS=""
HARDEN_FLAGS=""
while [[ $# -gt 0 ]]; do
case $1 in
@@ -32,6 +37,8 @@ while [[ $# -gt 0 ]]; do
echo " -h, --help Show this help"
echo " --clean Clean before build"
echo " --full Full rebuild (autoreconf + configure + make)"
echo " --asan Build with AddressSanitizer"
echo " --hardened Build with compiler security hardening flags"
echo " -j[N] Build with N parallel jobs (default: 4)"
exit 0
;;
@@ -43,6 +50,14 @@ while [[ $# -gt 0 ]]; do
FULL_REBUILD=1
shift
;;
--asan)
USE_ASAN=1
shift
;;
--hardened)
USE_HARDENED=1
shift
;;
-j*)
JOBS="$1"
shift
@@ -122,13 +137,43 @@ else
fi
fi
# Generate Makefiles if needed
if [ ! -f Makefile ]; then
echo ""
echo "Running ./configure..."
if [ "$USE_ASAN" -eq 1 ]; then
./configure CFLAGS="-g -Og -fsanitize=address -fno-omit-frame-pointer -fno-pie" LDFLAGS="-no-pie"
elif [ "$USE_HARDENED" -eq 1 ]; then
./configure --enable-hardening
else
./configure
fi
fi
# Build (all .o files go to build/ directory, binary copied to project root)
echo ""
echo "Compiling with $JOBS..."
BUILD_SUCCESS=0
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
if [ ${PIPESTATUS[0]} -eq 0 ]; then
if [ "$USE_ASAN" -eq 1 ]; then
echo "(AddressSanitizer enabled — lib/ and src/ only, skipping tests)"
AF="-fsanitize=address -fno-omit-frame-pointer -g -Og"
AL="-fsanitize=address"
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; }
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; }
BUILD_SUCCESS=1
elif [ "$USE_HARDENED" -eq 1 ]; then
echo "(Hardening flags enabled)"
HF="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE"
HL="-Wl,-z,relro -Wl,-z,now -pie"
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; }
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; }
(cd tests && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "tests build failed"; exit 1; }
BUILD_SUCCESS=1
else
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
if [ ${PIPESTATUS[0]} -eq 0 ]; then
BUILD_SUCCESS=1
fi
fi
if [ $BUILD_SUCCESS -eq 1 ]; then
+37 -6
View File
@@ -1,8 +1,24 @@
#!/bin/sh
#pkg install -y autoconf openssl
set -e
# Проверка и установка зависимостей
need_install=""
for cmd in autoreconf aclocal automake libtoolize; do
if ! command -v "$cmd" >/dev/null 2>&1; then
need_install=1
break
fi
done
if [ -n "$need_install" ]; then
echo "Устанавливаются необходимые пакеты..."
pkg update
pkg install -y autoconf automake libtool pkgconf openssl
fi
mkdir -p m4
set -e
echo "=== Полная пересборка uTun ==="
@@ -12,18 +28,33 @@ make distclean 2>/dev/null || true
rm -f config.h config.status config.log
rm -f configure.log autoreconf.log build.log
# 2. Автогенерация (autoreconf)
echo "[2/4] Автогенерация (autoreconf -fi)..."
# 2. Генерация configure
echo "[2/4] Автогенерация..."
autoreconf -fi 2>&1 | tee autoreconf.log
# Проверяем, появился ли configure
if [ ! -f configure ]; then
echo "Ошибка: autoreconf не создал configure."
exit 1
fi
chmod +x configure
# 3. Конфигурация
echo "[3/4] Конфигурация..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
# Определяем количество ядер
if command -v nproc >/dev/null 2>&1; then
JOBS=$(nproc)
else
JOBS=$(sysctl -n hw.ncpu)
fi
# 4. Сборка
echo "[4/4] Сборка..."
make -j$(nproc) 2>&1 | tee build.log
make -j"$JOBS" 2>&1 | tee build.log
echo ""
echo
echo "=== Готово ==="
echo "Бинарный файл: src/utun"
+1 -1
View File
@@ -70,7 +70,7 @@ echo "Building utun.exe..."
cd src
EXE_NEEDS_REBUILD=0
CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c route_lib.c route_bgp.c routing.c tun_if.c tun_route.c etcp.c etcp_connections.c etcp_loadbalancer.c secure_channel.c crc32.c pkt_normalizer.c etcp_api.c tun_windows.c"
CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c route_lib.c topo_bgp.c routing.c tun_if.c tun_route.c etcp.c etcp_connections.c etcp_loadbalancer.c secure_channel.c crc32.c pkt_normalizer.c etcp_api.c tun_windows.c"
for src in $CORE_SOURCES; do
if [ -f "$src" ]; then
+1 -1
View File
@@ -30,4 +30,4 @@ REM Launch MSYS2 UCRT64 shell and run build script with --full flag
echo Starting MSYS2 UCRT64 environment for FULL REBUILD...
echo Running: bash build.sh --full
echo.
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash build.sh --full 2>&1 | tee build_full.log"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh --full 2>&1 | tee build_full.log"
File diff suppressed because it is too large. Load diff
+14
View File
@@ -55,6 +55,20 @@ fi
AM_CONDITIONAL([USE_OPENSSL], [test "x$with_openssl" = "xyes"])
# ==================== HARDENING ====================
AC_ARG_ENABLE([hardening],
AS_HELP_STRING([--enable-hardening], [Enable compiler security hardening (FORTIFY, stack protector, PIE, RELRO)]),
[enable_hardening=$enableval],
[enable_hardening=no])
if test "x$enable_hardening" = "xyes"; then
HARDEN_CFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE"
HARDEN_LDFLAGS="-Wl,-z,relro -Wl,-z,now -pie"
CFLAGS="$CFLAGS $HARDEN_CFLAGS"
LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS"
AC_MSG_NOTICE([Hardening flags enabled: $HARDEN_CFLAGS])
fi
# ==================== НАДЁЖНЫЙ ДЕТЕКТ WINDOWS ====================
AC_MSG_CHECKING([for Windows])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+284
View File
@@ -0,0 +1,284 @@
#!/bin/bash
# cross-build-win.sh — кросс-компиляция uTun для Windows 10 из Linux
# Использование: ./cross-build-win.sh [--clean]
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
step() { echo -e "${CYAN}===${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARNING]${NC} $*"; }
fatal() { echo -e "${RED}FATAL:${NC} $*" >&2; exit 1; }
trap 'fatal "script failed at line $LINENO (exit code: $?)"' ERR
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SCRIPT_DIR/build-win"
PREFIX="$BUILD_DIR/prefix"
MINGW_CC="x86_64-w64-mingw32-gcc"
MINGW_AR="x86_64-w64-mingw32-ar"
MINGW_RANLIB="x86_64-w64-mingw32-ranlib"
MINGW_PKGS="gcc-mingw-w64-x86-64-posix g++-mingw-w64-x86-64-posix"
OPENSSL_VER="3.0.16"
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz"
# -------------------------------------------------------------------
# 0. Аргументы
# -------------------------------------------------------------------
CLEAN=0
while [[ $# -gt 0 ]]; do
case "$1" in
--clean) CLEAN=1; shift ;;
*) fatal "unknown option: $1" ;;
esac
done
if [ "$CLEAN" -eq 1 ]; then
echo -e "${YELLOW}Cleaning build directory: $BUILD_DIR${NC}"
rm -rf "$BUILD_DIR"
fi
echo -e "${CYAN}==============================================${NC}"
echo -e "${CYAN} uTun Cross-Compilation for Windows 10${NC}"
echo -e "${CYAN}==============================================${NC}"
echo ""
# -------------------------------------------------------------------
# 1. Проверка системы
# -------------------------------------------------------------------
step "Checking system..."
if [ "$(uname -s)" != "Linux" ]; then
fatal "This script only runs on Linux. Detected: $(uname -s)"
fi
if [ "$(uname -m)" != "x86_64" ]; then
fatal "This script requires x86_64 architecture. Detected: $(uname -m)"
fi
if ! command -v apt-get &>/dev/null; then
fatal "apt-get not found. Only Debian/Ubuntu are supported."
fi
ok "System: Linux x86_64 with apt"
# -------------------------------------------------------------------
# 2. Установка пакетов
# -------------------------------------------------------------------
step "Installing required packages..."
need_install=()
for pkg in $MINGW_PKGS; do
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
need_install+=("$pkg")
fi
done
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then
need_install+=("wget")
fi
if [ ${#need_install[@]} -gt 0 ]; then
echo " Installing: ${need_install[*]}"
sudo apt-get update -qq || fatal "apt-get update failed"
sudo apt-get install -y -qq "${need_install[@]}" || fatal "apt-get install failed"
ok "Packages installed: ${need_install[*]}"
else
ok "All packages already installed"
fi
# Проверка компилятора
for tool in "$MINGW_CC" "$MINGW_AR" "$MINGW_RANLIB"; do
if ! command -v "$tool" &>/dev/null; then
fatal "$tool not found after package installation"
fi
done
ok "Cross-compiler: $($MINGW_CC --version | head -1)"
# -------------------------------------------------------------------
# 3. Кросс-компиляция OpenSSL
# -------------------------------------------------------------------
OPENSSL_SRC="$BUILD_DIR/openssl-${OPENSSL_VER}"
LIBCRYPTO="$PREFIX/lib/libcrypto.a"
if [ -f "$LIBCRYPTO" ]; then
ok "OpenSSL already built: $LIBCRYPTO"
else
step "Building OpenSSL ${OPENSSL_VER} for Windows..."
mkdir -p "$BUILD_DIR"
TARBALL="$BUILD_DIR/openssl-${OPENSSL_VER}.tar.gz"
if [ ! -f "$TARBALL" ]; then
echo " Downloading OpenSSL ${OPENSSL_VER}..."
if command -v wget &>/dev/null; then
wget -q --show-progress -O "$TARBALL" "$OPENSSL_URL" || fatal "wget download failed for $OPENSSL_URL"
else
curl -L --progress-bar -o "$TARBALL" "$OPENSSL_URL" || fatal "curl download failed for $OPENSSL_URL"
fi
ok "Downloaded: $TARBALL"
fi
if [ ! -d "$OPENSSL_SRC" ]; then
echo " Extracting..."
tar xzf "$TARBALL" -C "$BUILD_DIR" || fatal "tar extract failed"
fi
echo " Configuring OpenSSL for mingw64..."
cd "$OPENSSL_SRC"
./Configure --cross-compile-prefix=x86_64-w64-mingw32- \
mingw64 no-shared no-tests \
--prefix="$PREFIX" --libdir="$PREFIX/lib" --openssldir="$PREFIX/ssl" \
|| fatal "OpenSSL configure failed (see output above)"
ok "OpenSSL configured"
echo " Compiling OpenSSL..."
make -j"$(nproc)" || fatal "OpenSSL make failed"
echo " Installing OpenSSL..."
make install_sw >/dev/null || fatal "OpenSSL make install_sw failed"
cd "$SCRIPT_DIR"
# OpenSSL может установить в lib64/ вместо lib/ — поправим
if [ ! -f "$LIBCRYPTO" ] && [ -f "$PREFIX/lib64/libcrypto.a" ]; then
ln -sf lib64 "$PREFIX/lib" 2>/dev/null || cp -r "$PREFIX/lib64" "$PREFIX/lib" 2>/dev/null || true
fi
if [ ! -f "$LIBCRYPTO" ]; then
fatal "libcrypto.a was not found at $PREFIX/lib/ or $PREFIX/lib64/"
fi
ok "OpenSSL built: $LIBCRYPTO"
fi
# -------------------------------------------------------------------
# 4. Сборка libuasync.a
# -------------------------------------------------------------------
step "Building libuasync.a..."
LIB_OBJDIR="$BUILD_DIR/lib-objs"
mkdir -p "$LIB_OBJDIR"
# Все .c файлы из lib/Makefile.am + myip.c (реализация get_my_ip)
LIB_SOURCES=(
u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c
sha256.c mem.c serialize.c swm_min.c getmyip.c radix.c tcp_io.c
myip.c platform_compat.c socket_compat.c
liblmdb/mdb.c liblmdb/midl.c
)
LIB_CFLAGS="-D_ISOC99_SOURCE -DDEBUG_OUTPUT_STDERR -DUSE_OPENSSL"
LIB_CFLAGS+=" -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib/liblmdb"
LIB_CFLAGS+=" -I$PREFIX/include"
LIB_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
for src in "${LIB_SOURCES[@]}"; do
obj="$LIB_OBJDIR/$(basename "${src%.c}.o")"
echo " CC lib/$src"
"$MINGW_CC" $LIB_CFLAGS -c "$SCRIPT_DIR/lib/$src" -o "$obj" \
|| fatal "compilation failed: lib/$src"
done
"$MINGW_AR" crs "$BUILD_DIR/libuasync.a" "$LIB_OBJDIR"/*.o \
|| fatal "ar failed for libuasync.a"
"$MINGW_RANLIB" "$BUILD_DIR/libuasync.a" 2>/dev/null || true
ok "libuasync.a ($(wc -c < "$BUILD_DIR/libuasync.a") bytes)"
# -------------------------------------------------------------------
# 5. Сборка объектных файлов для utun.exe
# -------------------------------------------------------------------
step "Compiling utun source files..."
SRC_OBJDIR="$BUILD_DIR/src-objs"
mkdir -p "$SRC_OBJDIR"
# Все .c файлы из src/Makefile.am (utun_CORE_SOURCES)
SRC_SOURCES=(
utun.c utun_instance.c config_parser.c config_updater.c
route_lib.c route6_lib.c topo_bgp.c route_ping.c
topo_node.c topo_node_lmdb.c route_connectivity.c conn_mgr.c
routing.c tun_if.c tun_route.c tun_linux.c tun_freebsd.c tun_windows.c
etcp.c etcp_connections.c etcp_bbr.c etcp_loadbalancer.c
etcp_debug.c etcp_dump.c secure_channel.c crc32.c
stcp_link.c stcp.c stcp_server.c stcp_client.c
pkt_normalizer.c packet_dump.c etcp_api.c
control_server.c msg_transport.c
firewall.c eim_nat.c nat_transport.c dummynet.c
etcp_router.c
proxy/udp_proxy.c proxy/socks_proxy.c
lwip_tcp/lwip_pbuf.c
)
SRC_CFLAGS="-DUSE_OPENSSL"
SRC_CFLAGS+=" -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/lib/liblmdb"
SRC_CFLAGS+=" -I$SCRIPT_DIR/tinycrypt/lib/include -I$SCRIPT_DIR/tinycrypt/lib/source"
SRC_CFLAGS+=" -I$PREFIX/include"
SRC_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
TOTAL=${#SRC_SOURCES[@]}
CURRENT=0
FAILED=()
for src in "${SRC_SOURCES[@]}"; do
CURRENT=$((CURRENT + 1))
# Имя объектного файла: заменяем / на _ (для proxy/ и lwip_tcp/)
obj_name="$(echo "$src" | tr '/' '_')"
obj="$SRC_OBJDIR/${obj_name%.c}.o"
printf " [%3d/%3d] CC src/%s\n" "$CURRENT" "$TOTAL" "$src"
if ! "$MINGW_CC" $SRC_CFLAGS -c "$SCRIPT_DIR/src/$src" -o "$obj" 2>&1; then
FAILED+=("src/$src")
fi
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo ""
fatal "compilation failed for: ${FAILED[*]}"
fi
ok "All $TOTAL source files compiled"
# -------------------------------------------------------------------
# 6. Линковка utun.exe
# -------------------------------------------------------------------
step "Linking utun.exe..."
"$MINGW_CC" -o "$SCRIPT_DIR/utun.exe" \
"$SRC_OBJDIR"/*.o \
"$BUILD_DIR/libuasync.a" \
"$LIBCRYPTO" \
-lws2_32 -liphlpapi -ladvapi32 -lbcrypt -lcrypt32 -lpthread -lm \
-static \
|| fatal "linking failed (see undefined references above)"
ok "utun.exe ($(wc -c < "$SCRIPT_DIR/utun.exe") bytes)"
# -------------------------------------------------------------------
# 7. Копирование wintun.dll и финал
# -------------------------------------------------------------------
echo ""
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
cp "$SCRIPT_DIR/lib/wintun.dll" "$SCRIPT_DIR/"
ok "wintun.dll copied to project root"
else
warn "wintun.dll not found in lib/ — place it manually alongside utun.exe"
fi
echo ""
echo -e "${GREEN}==============================================${NC}"
echo -e "${GREEN} Build successful!${NC}"
echo -e "${GREEN} Binary: utun.exe${NC}"
echo -e "${GREEN}==============================================${NC}"
echo ""
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
echo "Run on Windows:"
echo " 1. Install Wintun driver (https://www.wintun.net)"
echo " 2. Copy utun.exe + wintun.dll to the same directory"
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
else
echo "Run on Windows:"
echo " 1. Install Wintun driver (https://www.wintun.net)"
echo " 2. Copy utun.exe + wintun.dll to the same directory"
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
fi
echo ""
exit 0
+35
View File
@@ -0,0 +1,35 @@
План доработок
[ ] продумать архитектуру репликации таблицы узлов группы
[ ] добавить узлы хелперы (для проксирования трафика)
Категории узлов:
- узлы без нат []
- узлы с nat [restricted]
Узлы за nat сами подключаются к прямым
Узлы за eim nat сами подключаются и сообщают обновление адреса.
Прямые узлы должны сами должны объединиться в сеть следующим образом:
Инит: база содержит RTT по узлам.
выбираем n=16 узлов с минимальным rtt. запускаем подключение к ним.
синхронизируем роутинг таблицу с ними (обмен маршрутами должен инициироваться сразу после подключения).
при потере связи узлы через nat должны найти альтернативы.
в таблице обновляется
как обмен завершен
Нужно собирать и обновлять информацию о том
- к каким публичным нодам подключены nat узлы.
- метрика доступности узла - как давно не менялся ip:port - собираем с клиентов. вместе с рейтигном (каждый клиент по узлу фолрмирует свой рейтинг кандидата в суперузлы и отправляет подписанный рейтинг узлу)
рейтинг включает:
- число разных часов фигурирует в замерах
- число разных дат фигурирует в замерах
- гистограмма rtt: 0-2ms, 2-4ms, 4-6ms, 6-10ms, 10-13ms, 14-18ms, 18-25ms, 25-35ms, 35-50ms, 50-75ms, 75-100ms, 100-130ms, 130-160ms, 160+ms
- гистограмма потерь: 0-1% 1-2% 2-4% 4-8% >8% (обновляется каждый час)
Сделать подключение к группе
+32 -8
View File
@@ -22,12 +22,9 @@ utun поддерживает встроенный ipv4 роутинг, може
Есть статические подключения (которые прописаны в конфиге) и есть динамические - которые устанавливаются в процессе по необходимости.
Обмен маршрутами происходит только через статические подключения.
toto:
- задать ограничение bandwidth для каждого сокета (общее).
## etcp_connections:
- обслуживает encrypt/decrypt и установку защищенного подключения.
- обслуживает encrypt/decrypt и установку защищенного подключения per-link.
- для одного etcp соединения может испольоваться несколько подключений одновременно (load balancing / filover)
## etcp_loadbalancer:
@@ -50,12 +47,39 @@ toto:
- обеспечивает ретрансмиссии при передаче и сборку в правильной последовательности при приёме
- взаимодействует с pkt_normalizer и с udp сокетами для отправки-получения пакетов
## etcp_metric:
- обеспечивает обновление метрик каналов etcp_connections
- вызывается при получении ack
## stcp_* :
- аналогично etcp только упрощенный вариант, используя tcp в качестве транспорта.
## etcp_router:
- устанавливает связь между узлами, маршрутизирует трафик через цепочку узлов
## route_lib, route6_lib :
- управляет таблицами маршрутизации (ipv4/ipv6 lookup)
## route_node:
- хранит список доступных узлов (полученные от соседей) и их адреса/pubkeys для подключения.
- содержит апи для поиска-чтения-обновления узла
## route_bgp:
- составляет карту маршрутизации между узлами и обновляет ее при подключении-отключении узлов
## route_ping:
- модуль для определения типа nat (strict или eim) используя вспомогательный узел
Библиотеки:
## uasync
- модуль для асинхронной работы.
реализует сокеты, таймауты и сигналы из других потоков
- быстро работает с большим числом таймаутов (timeout_heap)
## ll_queue
- модуль работы с очередями, использует uasync.
- реализует fifo/lifo очереди, быстрый поиск по индексу, различные callback - сигнализация о состояниях очереди,
- congestion control: если много желающих записать -> очередь round-robin
Тесты:
# Правила работы с очередями в тестах:
- для добавления в очередь надо использовать queue_wait_threshold(q,0,0,arg) и добавлять по одному пакету, каждый раз дожидаясь когда очередь станет пустой.
- для добавления в очередь надо использовать queue_waiter_wait и queue_set_threshold, добавлять по одному пакету, каждый раз дожидаясь когда очередь станет пустой.
- для получения надо использовать queue_set_callback + queue_resume_callback
+1 -1
View File
@@ -136,7 +136,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
- local_link_id: uint8_t (0-255), назначается отправителем
- sock_id: uint8_t, уникальный ID сокета отправителя
- only_local: uint8_t, флаг "только локальные подключения"
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
-164
View File
@@ -1,164 +0,0 @@
config.h
lib\debug_config.c
lib\debug_config.h
lib\ll_queue.c
lib\ll_queue.h
lib\mem.c
lib\mem.h
lib\memory_pool.c
lib\memory_pool.h
lib\platform_compat.c
lib\platform_compat.h
lib\sha256.c
lib\sha256.h
lib\socket_compat.c
lib\socket_compat.h
lib\timeout_heap.c
lib\timeout_heap.h
lib\u_async.c
lib\u_async.h
lib\wintun.h
net_emulator\net_emulator.c
net_emulator\net_emulator.h
src\config_parser.c
src\config_parser.h
src\config_updater.c
src\config_updater.h
src\control_server.c
src\control_server.h
src\crc32.c
src\crc32.h
src\dummynet.c
src\dummynet.h
src\etcp.c
src\etcp.h
src\etcp_api.c
src\etcp_api.h
src\etcp_connections.c
src\etcp_connections.h
src\etcp_debug.c
src\etcp_debug.h
src\etcp_loadbalancer.c
src\etcp_loadbalancer.h
src\packet_dump.c
src\packet_dump.h
src\pkt_normalizer.c
src\pkt_normalizer.h
src\route_bgp.c
src\route_bgp.h
src\route_lib.c
src\route_lib.h
src\routing.c
src\routing.h
src\secure_channel.c
src\secure_channel.h
src\tun_if.c
src\tun_if.h
src\tun_linux.c
src\tun_route.c
src\tun_route.h
src\tun_windows.c
src\utun.c
src\utun_instance.c
src\utun_instance.h
tests\bench_timeout_heap.c
tests\bench_uasync_timeouts.c
tests\debug_full_test.c
tests\debug_performance.c
tests\debug_simple.c
tests\detailed_test.c
tests\simple_test.c
tests\test_bgp_route_exchange.c
tests\test_config_debug.c
tests\test_control_server.c
tests\test_control_simple.c
tests\test_crash_debug.c
tests\test_crypto.c
tests\test_debug_categories.c
tests\test_dummynet.c
tests\test_ecc_encrypt.c
tests\test_etcp_100_packets.c
tests\test_etcp_api.c
tests\test_etcp_crypto.c
tests\test_etcp_dummynet.c
tests\test_etcp_exit.c
tests\test_etcp_link_id.c
tests\test_etcp_minimal.c
tests\test_etcp_simple_traffic.c
tests\test_etcp_two_instances.c
tests\test_intensive_memory_pool.c
tests\test_intensive_memory_pool_new.c
tests\test_ll_queue.c
tests\test_memory_pool_and_config.c
tests\test_minimal.c
tests\test_minimal_exit.c
tests\test_offset.c
tests\test_packet_dump.c
tests\test_pkt_normalizer_etcp.c
tests\test_pkt_normalizer_standalone.c
tests\test_poll_exact.c
tests\test_poll_multi.c
tests\test_route_lib.c
tests\test_routing_mesh.c
tests\test_simple.c
tests\test_simple2.c
tests\test_socket.c
tests\test_utils.h
tests\test_u_async_comprehensive.c
tests\test_u_async_performance.c
tests\track_test.c
tests\working_crypto_test.c
tinycrypt\lib\include\tinycrypt\aes.h
tinycrypt\lib\include\tinycrypt\cbc_mode.h
tinycrypt\lib\include\tinycrypt\ccm_mode.h
tinycrypt\lib\include\tinycrypt\cmac_mode.h
tinycrypt\lib\include\tinycrypt\constants.h
tinycrypt\lib\include\tinycrypt\ctr_mode.h
tinycrypt\lib\include\tinycrypt\ctr_prng.h
tinycrypt\lib\include\tinycrypt\ecc.h
tinycrypt\lib\include\tinycrypt\ecc_dh.h
tinycrypt\lib\include\tinycrypt\ecc_dsa.h
tinycrypt\lib\include\tinycrypt\ecc_platform_specific.h
tinycrypt\lib\include\tinycrypt\hmac.h
tinycrypt\lib\include\tinycrypt\hmac_prng.h
tinycrypt\lib\include\tinycrypt\sha256.h
tinycrypt\lib\include\tinycrypt\utils.h
tinycrypt\lib\source\aes_decrypt.c
tinycrypt\lib\source\aes_encrypt.c
tinycrypt\lib\source\cbc_mode.c
tinycrypt\lib\source\ccm_mode.c
tinycrypt\lib\source\cmac_mode.c
tinycrypt\lib\source\ctr_mode.c
tinycrypt\lib\source\ctr_prng.c
tinycrypt\lib\source\ecc.c
tinycrypt\lib\source\ecc_dh.c
tinycrypt\lib\source\ecc_dsa.c
tinycrypt\lib\source\ecc_platform_specific.c
tinycrypt\lib\source\hmac.c
tinycrypt\lib\source\hmac_prng.c
tinycrypt\lib\source\sha256.c
tinycrypt\lib\source\utils.c
tinycrypt\tests\test_aes.c
tinycrypt\tests\test_cbc_mode.c
tinycrypt\tests\test_ccm_mode.c
tinycrypt\tests\test_client_server.c
tinycrypt\tests\test_cmac_mode.c
tinycrypt\tests\test_ctr_mode.c
tinycrypt\tests\test_ctr_prng.c
tinycrypt\tests\test_ecc_dh.c
tinycrypt\tests\test_ecc_dsa.c
tinycrypt\tests\test_ecc_utils.c
tinycrypt\tests\test_hmac.c
tinycrypt\tests\test_hmac_prng.c
tinycrypt\tests\test_sha256.c
tinycrypt\tests\include\test_ecc_utils.h
tinycrypt\tests\include\test_utils.h
tools\bping\bping.c
tools\etcpmon\etcpmon_client.c
tools\etcpmon\etcpmon_client.h
tools\etcpmon\etcpmon_graph.c
tools\etcpmon\etcpmon_graph.h
tools\etcpmon\etcpmon_gui.c
tools\etcpmon\etcpmon_gui.h
tools\etcpmon\etcpmon_main.c
tools\etcpmon\etcpmon_protocol.h
+7 -2
View File
@@ -28,14 +28,19 @@ libuasync_a_SOURCES = \
serialize.c \
serialize.h \
tcp_io.c \
tcp_io.h
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:
+25 -28
View File
@@ -128,6 +128,8 @@ static const struct {
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
{"bbr", DEBUG_CATEGORY_BBR},
{"etcp_dump", DEBUG_CATEGORY_ETCP_DUMP},
{"connectivity", DEBUG_CATEGORY_CONNECTIVITY},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -136,13 +138,14 @@ static const struct {
const char* name;
debug_level_t level;
} g_level_table[] = {
{"none", DEBUG_LEVEL_NONE},
{"error", DEBUG_LEVEL_ERROR},
{"warn", DEBUG_LEVEL_WARN},
{"info", DEBUG_LEVEL_INFO},
{"debug", DEBUG_LEVEL_DEBUG},
{"trace", DEBUG_LEVEL_TRACE},
{NULL, DEBUG_LEVEL_NONE}
{"disabled", DEBUG_LEVEL_DISABLED},
{"none", DEBUG_LEVEL_NONE},
{"error", DEBUG_LEVEL_ERROR},
{"warn", DEBUG_LEVEL_WARN},
{"info", DEBUG_LEVEL_INFO},
{"debug", DEBUG_LEVEL_DEBUG},
{"trace", DEBUG_LEVEL_TRACE},
{NULL, DEBUG_LEVEL_NONE}
};
debug_category_t get_category_by_name(const char* name) {
@@ -162,7 +165,7 @@ debug_level_t debug_level_from_name(const char* name) {
return g_level_table[i].level;
}
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: none,error,warn,info,debug,trace)", name);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: disabled,none,error,warn,info,debug,trace)", name);
return DEBUG_LEVEL_INFO; /* fallback */
}
@@ -220,15 +223,13 @@ void debug_config_init(void) {
// Dual output defaults
g_debug_config.file_output = NULL; // No file by default
g_debug_config.file_level = DEBUG_LEVEL_TRACE; // File gets everything
g_debug_config.console_level = DEBUG_LEVEL_INFO; // Console gets INFO+
g_debug_config.console_enabled = 1; // Console enabled
g_debug_config.thread_marker = 0; // No thread marker by default
}
/* Set debug level */
void debug_set_level(debug_level_t level) {
g_debug_config.level = level;
g_debug_config.console_level = level;
}
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
@@ -280,16 +281,6 @@ void debug_set_output_file(const char* file_path) {
}
/* Set console log level */
void debug_set_console_level(debug_level_t level) {
g_debug_config.console_level = level;
}
/* Set file log level */
void debug_set_file_level(debug_level_t level) {
g_debug_config.file_level = level;
}
/* Enable/disable console output */
void debug_enable_console(int enable) {
g_debug_config.console_enabled = enable;
}
@@ -343,6 +334,9 @@ int debug_should_output(debug_level_t level, debug_category_t category_idx) {
}
debug_level_t cat_level = g_debug_config.category_levels[category_idx];
if (cat_level == DEBUG_LEVEL_DISABLED) {
return 0;
}
if (cat_level == DEBUG_LEVEL_NONE) {
cat_level = g_debug_config.level;
}
@@ -395,6 +389,12 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000);
remaining = BUFFER_SIZE - offset;
/* Add thread marker if set */
if (g_debug_config.thread_marker > 0) {
offset += snprintf(buffer + offset, remaining, "[%d] ", g_debug_config.thread_marker);
remaining = BUFFER_SIZE - offset;
}
/* Add level */
const char* level_name = get_level_name(level);
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name);
@@ -433,15 +433,12 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
va_end(args);
/* Output to console if enabled and level is sufficient */
if (g_debug_config.console_enabled && level <= g_debug_config.console_level) {
fprintf(stdout, "%s", buffer);
fflush(stdout);
}
/* Output to file if open and level is sufficient */
if (g_debug_config.file_output && level <= g_debug_config.file_level) {
if (g_debug_config.file_output) {
fprintf(g_debug_config.file_output, "%s", buffer);
fflush(g_debug_config.file_output);
} else if (g_debug_config.console_enabled) {
fprintf(stdout, "%s", buffer);
fflush(stdout);
}
#undef BUFFER_SIZE
}
+14 -13
View File
@@ -26,12 +26,13 @@ extern "C" {
/* Debug levels */
typedef enum {
DEBUG_LEVEL_NONE = 0, // No debug output
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Warnings and errors
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
DEBUG_LEVEL_DEBUG = 4, // Full debug output
DEBUG_LEVEL_TRACE = 5 // Trace everything
DEBUG_LEVEL_DISABLED = -1, // Category completely disabled, never outputs
DEBUG_LEVEL_NONE = 0, // Fallback to global level
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Warnings and errors
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
DEBUG_LEVEL_DEBUG = 4, // Full debug output
DEBUG_LEVEL_TRACE = 5 // Trace everything
} debug_level_t;
/* Debug categories as indices (0-based) */
@@ -61,7 +62,9 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
#define DEBUG_CATEGORY_ETCP_DUMP 24 // ETCP packet dump
#define DEBUG_CATEGORY_CONNECTIVITY 25 // Connection/handshake lifecycle
#define DEBUG_CATEGORY_COUNT 26 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
@@ -75,9 +78,8 @@ typedef struct {
// Dual output support
FILE* file_output; // File handle for log file (NULL if not open)
debug_level_t file_level; // Log level for file output
debug_level_t console_level; // Log level for console output
int console_enabled; // Enable console output
int thread_marker; // Thread identification (0=none, e.g. 2 for uasync)
} debug_config_t;
/* Global debug configuration */
@@ -87,6 +89,7 @@ extern debug_config_t g_debug_config;
void debug_config_init(void);
/* Set debug level */
// итоговый level = max (global level - здесь задается, category level)
void debug_set_level(debug_level_t level);
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
@@ -106,10 +109,8 @@ void debug_enable_function_name(int enable);
void debug_enable_file_line(int enable);
void debug_set_output_file(const char* file_path);
/* Dual output configuration */
void debug_set_console_level(debug_level_t level);
void debug_set_file_level(debug_level_t level);
void debug_enable_file_output(const char* file_path, int truncate);
/* Dual output configuration */
void debug_enable_file_output(const char* file_path, int truncate);
void debug_disable_file_output(void);
int debug_reopen_log(void);
void debug_enable_console(int enable);
+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>
+8 -4
View File
@@ -130,11 +130,11 @@ void queue_free(struct ll_queue* q) {
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
// Это упрощает архитектуру и предотвращает double-u_free
// Освободить список waiters (сбросить handle->internal)
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
struct queue_waiter* w = q->waiter_head;
while (w) {
struct queue_waiter* next = w->next;
if (w->handle) w->handle->internal = NULL;
if (w->handle) { w->handle->internal = NULL; if (w->handle->call_soon_id) { uasync_call_soon_cancel(q->ua, w->handle->call_soon_id); w->handle->call_soon_id = NULL; } }
u_free(w);
w = next;
}
@@ -305,14 +305,17 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
static void waiter_defer_cb(void* arg) {
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
h->defer_cb(h->defer_q, h->defer_arg);
h->call_soon_id = NULL;
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
else DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
}
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
struct queue_waiter_handle* h) {
if (q->waiter_defer) {
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_LL_QUEUE, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
uasync_call_soon(q->ua, h, waiter_defer_cb);
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
} else {
cb(q, arg);
}
@@ -692,6 +695,7 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
+10
View File
@@ -1,6 +1,11 @@
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h> // для size_t
#include <stdint.h> // для uint64_t
#include "memory_pool.h" // для struct memory_pool
@@ -117,6 +122,7 @@ struct queue_waiter_handle {
struct ll_queue* defer_q;
queue_threshold_callback_fn defer_cb;
void* defer_arg;
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
};
/**
@@ -411,4 +417,8 @@ static inline size_t queue_total_bytes(struct ll_queue* q) {
return q->total_bytes;
}
#ifdef __cplusplus
}
#endif
#endif // LL_QUEUE_H
+9
View File
@@ -2,6 +2,11 @@
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include "debug_config.h"
@@ -37,4 +42,8 @@ size_t memory_pool_get_total_free_blocks(void);
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
#ifdef __cplusplus
}
#endif
#endif // MEMORY_POOL_H
+30
View File
@@ -5,6 +5,11 @@
#ifndef PLATFORM_COMPAT_H
#define PLATFORM_COMPAT_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
@@ -30,6 +35,18 @@
// POSIX functions missing on Windows
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
// memmem — not available on Windows
#define memmem compat_memmem
static inline void* compat_memmem(const void* h, size_t hl, const void* n, size_t nl) {
if (nl == 0) return (void*)h;
if (hl < nl) return NULL;
const unsigned char* hay = (const unsigned char*)h;
const unsigned char* ndl = (const unsigned char*)n;
for (size_t i = 0; i <= hl - nl; i++)
if (hay[i] == ndl[0] && memcmp(hay + i, ndl, nl) == 0) return (void*)(hay + i);
return NULL;
}
// poll() and pollfd are already defined in winsock2.h on MSYS2 UCRT64
// Only define if not already present
@@ -174,4 +191,17 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
uint32_t get_default_route_netif_index(int family);
// Cross-platform mkdir
#ifdef _WIN32
#include <direct.h>
#define utun_mkdir(path, mode) _mkdir(path)
#else
#include <sys/stat.h>
#define utun_mkdir(path, mode) mkdir(path, mode)
#endif
#ifdef __cplusplus
}
#endif
#endif // PLATFORM_COMPAT_H
+9
View File
@@ -32,6 +32,15 @@
#ifndef _RADIX_H_
#define _RADIX_H_
#ifdef __cplusplus
extern "C" {
#ifdef __cplusplus
}
#endif
#endif
#include <stdint.h>
#include "mem.h"
+9
View File
@@ -76,6 +76,11 @@
*/
#ifndef SERIALIZE_H
#define SERIALIZE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "../lib/mem.h"
@@ -127,4 +132,8 @@ int serialize_decode(const uint8_t* in_buf, size_t in_len,
void serialize_free(const struct SerializeSchema* schema, void** structure);
#ifdef __cplusplus
}
#endif
#endif // SERIALIZE_H
+9
View File
@@ -9,6 +9,11 @@
#ifndef SC_SHA256_H
#define SC_SHA256_H
#ifdef __cplusplus
extern "C" {
#endif
/*************************** HEADER FILES ***************************/
#include <stddef.h>
#include <stdint.h>
@@ -29,4 +34,8 @@ void sc_sha256_init(SC_SHA256_CTX *ctx);
void sc_sha256_update(SC_SHA256_CTX *ctx, const uint8_t data[], size_t len);
void sc_sha256_final(SC_SHA256_CTX *ctx, uint8_t hash[]);
#ifdef __cplusplus
}
#endif
#endif // SC_SHA256_H
+1
View File
@@ -89,6 +89,7 @@ int socket_set_nonblocking(socket_t sock) {
}
int socket_close_wrapper(socket_t sock) {
if (sock == 0) return 0; // never close fd 0
return close(sock);
}
+9
View File
@@ -5,6 +5,11 @@
#ifndef SOCKET_COMPAT_H
#define SOCKET_COMPAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#ifdef _WIN32
@@ -73,4 +78,8 @@ const char* socket_strerror(int err);
// взять порт из sockaddr_storage
uint16_t ss_get_port(const struct sockaddr_storage *addr);
#ifdef __cplusplus
}
#endif
#endif // SOCKET_COMPAT_H
+34 -16
View File
@@ -13,6 +13,9 @@
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifdef _WIN32
#define SHUT_WR SD_SEND
#endif
static void read_cb(socket_t sock, void* arg);
static void write_cb(socket_t sock, void* arg);
@@ -32,7 +35,7 @@ struct tcp_conn* tcp_conn_create(
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg)
{
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", (void*)ua); return NULL; }
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
@@ -104,8 +107,17 @@ struct tcp_conn* tcp_conn_create(
return tc;
}
static void tcp_conn_deferred_free(void* arg) {
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
u_free(tc);
}
void tcp_conn_destroy(struct tcp_conn* tc) {
if (!tc) return;
if (tc->destroyed) return;
tc->destroyed = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
@@ -127,14 +139,12 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e); queue_resume_callback(tc->write_queue);
}
queue_free(tc->read_queue);
queue_free(tc->write_queue);
queue_free(tc->read_queue); tc->read_queue = NULL;
queue_free(tc->write_queue); tc->write_queue = NULL;
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
memory_pool_destroy(tc->entry_pool);
memory_pool_destroy(tc->data_pool);
u_free(tc);
if (tc->sock != SOCKET_INVALID) { socket_close_wrapper(tc->sock); tc->sock = SOCKET_INVALID; }
uasync_call_soon(tc->ua, tc, tcp_conn_deferred_free);
}
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
@@ -157,13 +167,15 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
}
}
if (tc->socket_id) {
if (tc->sock != SOCKET_INVALID) {
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
}
if (tc->sock != SOCKET_INVALID) {
#if HAS_EPOLL
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
#endif
socket_close_wrapper(tc->sock);
tc->sock = SOCKET_INVALID;
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
}
queue_set_callback(tc->write_queue, NULL, NULL);
@@ -183,7 +195,7 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
static void read_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
if (!tc || tc->sock == SOCKET_INVALID || !tc->read_queue) return;
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
uint8_t* buf = memory_pool_alloc(tc->data_pool);
@@ -200,8 +212,10 @@ static void read_cb(socket_t sock, void* arg) {
if (n > 0) {
e->dgram = buf;
e->len = (uint16_t)n;
queue_data_put(tc->read_queue, e);
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
struct ll_queue* rq = tc->read_queue;
queue_data_put(rq, e);
// tc мог быть уничтожен через callback chain — read_queue обнулён
if (rq && tc->read_queue && tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
tc->read_paused = 1;
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
@@ -366,12 +380,12 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
static void write_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
if (!tc || tc->sock == SOCKET_INVALID || !tc->write_queue) return;
if (!tc->connected) {
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0 && err == 0) {
tc->connected = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
} else {
@@ -384,6 +398,10 @@ static void write_cb(socket_t sock, void* arg) {
if (flush_write_buf(tc)) return;
if (!tc->write_buf) {
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
if (tc->write_queue->waiter_defer) {
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
tc->write_monitor = 0;
}
queue_resume_callback(tc->write_queue);
}
}
+10
View File
@@ -22,6 +22,11 @@
#ifndef TCP_IO_H
#define TCP_IO_H
#ifdef __cplusplus
extern "C" {
#endif
#include "u_async.h"
#include "socket_compat.h"
#include "ll_queue.h"
@@ -46,6 +51,7 @@ struct tcp_conn {
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
uint8_t closed; // сокет полностью закрыт (close)
uint8_t destroyed; // 1 = tcp_conn_destroy вызван, tc ожидает отложенного free
// Частичная отправка (из data_pool, не в очереди — досылается первой)
uint8_t* write_buf;
@@ -107,4 +113,8 @@ void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_con
// Принудительная остановка чтения: убирает EPOLLIN и отменяет read_waiter.
void tcp_conn_pause_read(struct tcp_conn* tc);
#ifdef __cplusplus
}
#endif
#endif
+9
View File
@@ -3,6 +3,11 @@
#ifndef TIMEOUT_HEAP_H
#define TIMEOUT_HEAP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> // For uint64_t
#include <stddef.h> // For size_t
@@ -94,4 +99,8 @@ size_t timeout_heap_get_freed_count(TimeoutHeap *h);
size_t timeout_heap_get_size(TimeoutHeap *h);
#ifdef __cplusplus
}
#endif
#endif // TIMEOUT_HEAP_H
+612 -603
View File
File diff suppressed because it is too large. Load diff
+79 -70
View File
@@ -5,18 +5,23 @@
#ifndef UASYNC_H
#define UASYNC_H
#include "platform_compat.h"
#include <stddef.h>
#include <signal.h>
#include "timeout_heap.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "platform_compat.h"
#include <stddef.h>
#include <signal.h>
#include "timeout_heap.h"
#include "socket_compat.h"
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket (for pipe/file)
typedef void (*socket_t_callback_t)(socket_t sock, void* user_arg);// передаёт user_arg из uasync_add_socket_t (for sockets)
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е
// Error type
typedef int err_t;
#define ERR_OK 0
@@ -28,21 +33,21 @@ typedef int err_t;
typedef void (*uasync_post_callback_t)(void* user_arg);
#include "memory_pool.h"
struct timeout_node; // Forward declaration
#include "memory_pool.h"
struct timeout_node; // Forward declaration
struct posted_task {
uasync_post_callback_t callback;
void* arg;
struct posted_task* next;
};
// Uasync instance structure
struct UASYNC {
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
struct timeout_node* immediate_queue_tail;
// Uasync instance structure
struct UASYNC {
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
struct timeout_node* immediate_queue_tail;
TimeoutHeap* timeout_heap; // Heap for timeout management
struct socket_array* sockets; // Array-based socket management
// Debug counters for memory allocation tracking
@@ -67,55 +72,55 @@ struct UASYNC {
#ifdef _WIN32
CRITICAL_SECTION posted_lock;
#else
pthread_mutex_t posted_lock;
#endif
volatile sig_atomic_t stop;
pthread_mutex_t posted_lock;
#endif
volatile sig_atomic_t stop;
};
// Type definitions
typedef struct UASYNC uasync_t;
typedef struct UASYNC UASYNC_t;
// Instance API - основной API для работы с uasync
struct UASYNC* uasync_create(void);
void uasync_destroy(struct UASYNC* ua, int close_fds);
// текущее время (timebase 0.1ms)
uint64_t get_time_tb(void);
// текущее время в микросекундах (для burst-измерений)
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);
// Immediate execution in next mainloop (FIFO order)
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback);
err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
// Type definitions
typedef struct UASYNC uasync_t;
typedef struct UASYNC UASYNC_t;
// Instance API - основной API для работы с uasync
struct UASYNC* uasync_create(void);
void uasync_destroy(struct UASYNC* ua, int close_fds);
// текущее время (timebase 0.1ms)
uint64_t get_time_tb(void);
// текущее время в микросекундах (для burst-измерений)
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);
// Immediate execution in next mainloop (FIFO order)
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback);
err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
// Sockets - for regular file descriptors (pipe, file)
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_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
// Динамическое управление мониторингом чтения/записи для сокета
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(struct UASYNC* ua, int timeout_tb);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(struct UASYNC* ua);
void uasync_stop(struct UASYNC* ua);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(struct UASYNC* ua, int timeout_tb);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(struct UASYNC* ua);
void uasync_stop(struct UASYNC* ua);
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
@@ -124,15 +129,19 @@ int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id);
// Print all resources (timers, sockets) for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix);
// Wakeup mechanism for interrupting poll
int uasync_wakeup(struct UASYNC* ua);
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
// сообщить async (из другого thread) чтобы он вызвал callback с аргументом
void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg);
// синхронизация памяти (для доступности из других потоков)
void uasync_memsync(struct UASYNC* ua);
#endif // UASYNC_H
// Wakeup mechanism for interrupting poll
int uasync_wakeup(struct UASYNC* ua);
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
// сообщить async (из другого thread) чтобы он вызвал callback с аргументом
void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg);
// синхронизация памяти (для доступности из других потоков)
void uasync_memsync(struct UASYNC* ua);
#ifdef __cplusplus
}
#endif
#endif // UASYNC_H
-219
View File
@@ -1,219 +0,0 @@
Диаграмма вызовов функций при определении типа NAT (NAT detection)
=====================================================================
=== ИНИЦИАЛИЗАЦИЯ BGP ===
route_bgp_init(instance)
└── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL)
└── При создании нового ETCP_CONN:
route_bgp_etcp_conn_cbk(conn, arg)
├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp)
└── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_REQUEST
├── etcp_connection_create(instance, name) [если новый peer]
│ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk]
├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1)
├── Отправляет ETCP_INIT_RESPONSE (с NAT info)
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── conn->ready_cbk
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) ===
etcp_connections_read_callback_socket(sock, arg)
└── Получает ETCP_INIT_RESPONSE
├── Сохраняет NAT address: link->nat_ip, link->nat_port
├── link->initialized = 1
└── etcp_conn_ready(link->etcp)
└── etcp_on_up(etcp)
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) ===
route_bgp_on_conn_up(etcp, arg)
└── route_bgp_new_conn(conn)
├── route_bgp_add_to_senders(bgp, conn)
└── Сканирует ВСЕ линки ВСЕХ соединений:
if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS)
└── route_bgp_start_link_nat_check(bgp, link)
├── if (nat_check_status == IN_PROGRESS) return
├── route_bgp_find_third_node(bgp, exclude=link->etcp)
│ └── Ищет другой ETCP_CONN в senders_list
├── Определяет target_ip/target_port:
│ ├── Если link->nat_ip != 0: использует nat_ip/nat_port
│ └── Иначе: использует link->remote_addr
├── Проверяет is_local_subnet(target_ip) [если запрещено - skip]
├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port)
│ └── Ищет первый линк с nat_ip != 0
├── Создает nat_check_arg { link, nat_ip, nat_port }
└── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port,
count=3, interval=500ms, timeout=1000ms,
wait_timeout=5000ms,
cb=nat_link_check_cb, arg=nat_check_arg,
pubkey=peer_pubkey)
=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) ===
route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...)
├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey }
├── etcp_send(to_conn, entry) [отправляет запрос third node]
├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer }
└── Добавляет в bgp->ping_pending
=== ОБРАБОТКА PING_REQ (на third node) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_REQ
└── route_ping_handle_req(bgp, from_conn, data, len)
├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total }
├── Находит первый IPv4 ETCP_SOCKET -> local_sock
└── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr,
timeout_ms, route_ping_single_cb, ctx, NULL, 0)
└── Отправляет ETCP пинг на target_addr
=== ОТВЕТ НА ПИНГ (целевой узел) ===
etcp_connections_read_callback_socket()
└── Получает ETCP пинг -> отправляет pong
=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) ===
route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len)
├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt
├── Если count_sent < count_total:
│ └── etcp_send_ping_to_socket(...) [следующий пинг]
└── Иначе:
└── route_ping_series_finish(ctx)
├── Вычисляет avg_rtt
└── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt }
└── etcp_send(reply_conn, entry) [отправляет результат инициатору]
=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_PING_RESP
└── route_ping_handle_resp(bgp, from_conn, data, len)
├── Ищет route_ping_pending по request_id
├── Отменяет timeout_timer
└── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
=== CALLBACK ПРОВЕРКИ NAT ===
nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED
├── link->nat_type = nat_type
├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED
└── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type)
├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type }
└── etcp_send(conn, entry) [отправляет результат клиенту]
=== ПРИЕМ NAT_INFO (на клиенте) ===
route_bgp_receive_cbk(data, len, from_conn, bgp)
└── ROUTE_SUBCMD_NAT_INFO
└── route_bgp_handle_nat_info(bgp, from_conn, data, len)
├── Ищет линки по remote_socket_id
├── Устанавливает link->nat_type = info->nat_type
└── Логирует результат NAT detection
=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) ===
route_bgp_send_nat_check_req(conn, socket_id)
├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port }
│ ├── Ищет линк с remote_socket_id == socket_id
│ └── Берет interface_addr из линка -> interface_ip/interface_port
└── etcp_send(conn, entry)
route_bgp_handle_nat_check_req(bgp, from_conn, data, len)
├── Принимает BGP_NAT_CHECK_REQ
├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy
├── Ищет линк с remote_socket_id == req->socket_id
└── Если линк найден и не в процессе проверки:
└── route_bgp_start_link_nat_check(bgp, target_link)
[далее по стандартному flow]
=== СВЯЗЬ СТРУКТУР ===
struct ETCP_SOCKET
├── local_addr [адрес бинда из конфига]
├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг]
└── ...
struct ETCP_LINK
├── remote_addr [адрес удаленного узла]
├── nat_ip [NAT IP, полученный из INIT_RESPONSE]
├── nat_port [NAT port, полученный из INIT_RESPONSE]
├── nat_type [OPEN/RESTRICTED, результат проверки]
├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED]
├── initialized [0/1, флаг завершения handshake]
└── ...
struct ETCP_CONN
├── links [список ETCP_LINK]
├── up_cbk [route_bgp_on_conn_up]
├── down_cbk [route_bgp_on_conn_down]
├── ready_cbk [вызывается при etcp_conn_ready]
├── initialized [0/1, флаг готовности]
├── links_up [0/1, есть ли активные линки]
└── ...
=== СТАТУСЫ NAT CHECK ===
NAT_CHECK_NONE = 0 [не проверялся]
NAT_CHECK_WAITING = 1 [ожидает]
NAT_CHECK_IN_PROGRESS = 2 [проверка запущена]
NAT_CHECK_OPEN = 3 [результат: OPEN]
NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED]
NAT_TYPE_UNKNOWN = 0
NAT_TYPE_OPEN = 1
NAT_TYPE_RESTRICTED = 2
NAT_VERIFIED_UNKNOWN = 4
NAT_VERIFIED_OPEN = 5
NAT_VERIFIED_RESTRICTED = 6
NAT_VERIFIED_DIRECT = 7
=== СООБЩЕНИЯ ПРОТОКОЛА ===
ETCP_INIT_REQUEST (0x02) [клиент -> сервер]
ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info]
ETCP_INIT_REQUEST_NOINIT (0x04)
ETCP_INIT_RESPONSE_NOINIT (0x05)
ETCP_PING (0x06)
ETCP_PONG (0x07)
ROUTE_SUBCMD_NODEINFO (0x04)
ROUTE_SUBCMD_REQUEST_TABLE (0x05)
ROUTE_SUBCMD_WITHDRAW (0x06)
ROUTE_SUBCMD_PING_REQ (0x07)
ROUTE_SUBCMD_PING_RESP (0x08)
ROUTE_SUBCMD_NAT_INFO (0x09)
ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A)
=== ПОЛНЫЙ FLOW (одна строка) ===
Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info
NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow]
+29
View File
@@ -0,0 +1,29 @@
UTUN
Идея: объединить узлы в единую локальную сеть.
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
Похож на libp2p но трафик превращает в случайную последовательность кторую сложно классифицировать
Потенциальные варианты использования:
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
- CDN. узлы - потребители контента - это мемберы сети, которые распространяют трафик.
То есть задача utun - обеспечить связность группы узлов и адаптацию работы всей сети к подключению-отключении отдельных узлов.
Механизмы:
1. Пробитие nat.
2. автообновление списка узлов и их адресов для подключения
3. шифрование трафика и маскировка handshake.
handshake просиходит без сигнатур по которым можно опреедлить тип сервиса, весь трафик выглядлит как случайный набор байт случайного размера
4. многоканальные подключения, агрегирование каналов, исключение мёртвых каналов
подключиться к узлу можно через несколько линков. UTUN будет объединять пропускную способность каналов, наблюдая за состоянием каналов, не допуская перегрузки и обеспечивая быстрые переповторы через другие каналы при потерях или большой задержке.
5. динамическая маршрутизация. Узлы могут назначать себе IP адреса и при подключении к сети анонсировать свои адреса/подсети.
это максимально упрощает добавление нового узла в сеть.
6. автоматический роутинг между узлами: узлы маршрутизируют трафик между собой.
используя статические подключения, прописанные в конфиге.
7. прямые подключения и поиск лучших подключений
узлы могут подключаться друг к другу напрямую, либо если оба узла за строгим NAT - выбирать оптимального посредника.
Узлы определяют свой тип подключения и NAT. Например у ростелеком - EIM NAT который можно использовать и для входящих подклюений.
Можно использовать IPv6.
+239
View File
@@ -0,0 +1,239 @@
Вот подробное описание работы модуля динамической маршрутизации.
---
## Архитектура: три слоя
```
┌─────────────────────────────────────────────────┐
│ ЯДРО ОС (netlink / ip route) │ ← tun_route.c — только при старте/стопе
│ НЕ динамически, только для configured subnets │
├─────────────────────────────────────────────────┤
│ ПЕРЕСЫЛКА ПАКЕТОВ │ ← routing.c
│ route_pkt() → route_lookup() → etcp_route_send │ или queue_data_put в TUN
├─────────────────────────────────────────────────┤
│ ТАБЛИЦА МАРШРУТОВ (ROUTE_TABLE) │ ← route_lib.c
│ route_insert / route_delete / route_lookup │ LPM через бинарный поиск
├─────────────────────────────────────────────────┤
│ BGP-ОБМЕН (ROUTE_BGP) │ ← route_bgp.c
│ NODEINFO (анонс) / WITHDRAW (отзыв) / │ broadcast / loop prevention
│ ping (NAT-детекция) │
└─────────────────────────────────────────────────┘
```
## Ключевые структуры данных
### `ROUTE_TABLE` (`route_lib.h:46`)
```c
struct ROUTE_TABLE {
struct ROUTE_ENTRY *entries; // отсортированный плоский массив
size_t count, capacity;
};
```
Маршруты хранятся в **отсортированном плоском массиве** (не trie, не radix). Сортировка: `network ASC`, затем `prefix_length DESC`. Это позволяет LPM через бинарный поиск.
### `ROUTE_ENTRY` (`route_lib.h:34`)
```c
struct ROUTE_ENTRY {
uint32_t network; // host byte order
uint8_t prefix_length;
struct NODEINFO_Q* v_node_info; // NULL = локальный маршрут
};
```
Каждая запись ссылается на узел-владелец (`NODEINFO_Q`). Все маршруты одного узла удаляются за один проход по этому указателю.
### `NODEINFO` (`route_node.h:73`)
Динамическая структура (packed) с полями фиксированной части + динамический хвост: имя, метаданные сокетов, IPv4/IPv6 адреса, подсети, транзитные узлы, hop_list. Передаётся по BGP как NODEINFO-пакет.
### `NODEINFO_Q` (`route_node.h:151`)
Локальная обёртка вокруг `NODEINFO`:
```c
struct NODEINFO_Q {
struct ll_entry ll; // linkage в очереди bgp->nodes (хэш-индекс по node_id)
struct ll_queue* paths; // список NODEINFO_PATH — по каким соединениям доступен узел
uint8_t last_ver; // последняя известная версия NODEINFO
struct NODE_CONNECTIVITY connectivity; // локальное состояние проб (ping/RTT)
struct NODEINFO node; // всегда в конце, динамический размер
};
```
### `ROUTE_BGP` (`route_bgp.h:90`)
```c
struct ROUTE_BGP {
struct ll_queue* senders_list; // ROUTE_BGP_CONN_ITEM — все активные пиры для broadcast
struct ll_queue* nodes; // хэш-индексированная очередь NODEINFO_Q
struct NODEINFO_Q* local_node; // свой собственный узел
// ... ping_pending, LMDB и т.д.
};
```
---
## Добавление маршрутов
Есть **три сценария** попадания маршрутов в таблицу:
### 1. Получение NODEINFO от пира (основной путь)
**Точка входа:** `route_bgp_receive_cbk()` (`route_bgp.c:162`) → `route_bgp_process_nodeinfo()` (`route_bgp.c:750`).
Алгоритм `route_bgp_process_nodeinfo`:
1. **Валидация:** проверка размера, `hop_count < MAX_HOPS(16)`, не свой ли node_id
2. **Проверка версии:** `nodeinfo1->last_ver >= new_ver` — значит версия та же или новее → только обновляем paths (через какой conn доступен), маршруты не трогаем
3. **Новая версия или новый узел:**
- Выделяется/перевыделяется `NODEINFO_Q` (если изменился динамический размер)
- Копируется NODEINFO + динамические данные
- Источник (`from->peer_node_id`) добавляется в конец hop_list (инкремент `hop_count`)
- Старый путь удаляется (`route_bgp_remove_path_by_hop`), новый добавляется (`route_bgp_add_path`)
- **`route_insert(bgp->instance->rt, nodeinfo1)`** ← вот здесь маршруты попадают в таблицу
- Сохраняется в LMDB
4. **Broadcast:** рассылается всем пирам кроме тех, кто уже в hop_list (loop prevention)
5. **Connectivity probe:** для новых или изменившихся узлов запускается зондирование связности
#### `route_insert()` (`route_lib.c:148`) — что происходит внутри:
```c
bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node)
```
1. Вызывает `get_node_routes(node, &subnets)` — получает указатель на IPv4-подсети внутри динамических данных NODEINFO (без копирования)
2. Конвертирует адреса из `network byte order → host byte order` через `ntohl()`
3. Проверяет **пересечения** с существующими маршрутами (точное совпадение network+prefix)
4. При необходимости расширяет массив entries (`u_realloc`, ×2)
5. Для каждой подсети:
- Находит позицию вставки бинарным поиском (`binary_search_insert_pos`): по `network ASC`, затем `prefix_length DESC`
- Сдвигает элементы вправо через `memmove()`
- Записывает `entry->network`, `entry->prefix_length`, `entry->v_node_info = node`
6. Инкрементирует `stats.local_routes` (если `hop_count==0`) или `stats.learned_routes`
**Важно:** при обновлении существующего узла (realloc) старые маршруты **сначала удаляются** (`route_delete`), потом вставляются новые. Это на случай если изменился состав подсетей.
### 2. Обновление своего local_node (`route_node.c`)
`route_bgp_update_my_nodeinfo()` вызывается при:
- Инициализации BGP
- Изменении конфигурации
- NAT-детекции (изменился `nat_type` сокета)
Алгоритм:
1. Считает сокеты, адреса, подсети из конфига
2. Сравнивает с текущим `local_node` — если изменилось:
- **`route_delete(rt, local_node)`** — удаляет старые локальные маршруты
- Освобождает старый `local_node`, выделяет новый
- Заполняет NODEINFO (node_id, hop_count=0, ключи, подсети)
- Инкрементирует `ver`
- **`route_insert(rt, local_node)`** — вставляет новые
3. Если изменился состав адресов — помечает `dirty=1`, рассылает обновлённый NODEINFO
### 3. Получение полной таблицы (синхронизация при connect)
При установке соединения:
1. `route_bgp_new_conn()` (`route_bgp.c:372`) добавляет conn в `senders_list` и отправляет `ROUTE_SUBCMD_REQUEST_TABLE`
2. Пир отвечает `route_bgp_handle_request_table()` (`route_bgp.c:1015`):
- Шлёт свой `local_node`
- Шлёт все известные узлы из `bgp->nodes` (с loop prevention через hop_list)
- Шлёт `ROUTE_SUBCMD_TABLE_COMPLETE`
3. Каждый полученный NODEINFO обрабатывается через `route_bgp_process_nodeinfo` → `route_insert`
---
## Удаление маршрутов
Есть **два сценария**:
### 1. Получен WITHDRAW от пира
**Точка входа:** `route_bgp_process_withdraw()` (`route_bgp.c:890`).
Алгоритм:
1. Извлекает `node_id` (кого удаляем) и `wd_source` (кто инициировал)
2. Ищет `NODEINFO_Q` по `node_id`
3. Вызывает `route_bgp_remove_path_by_hop(nq, wd_source)` — удаляет все пути, в hop_list которых есть `wd_source`
4. Если **путей не осталось** (unreachable):
- **`route_delete(bgp->instance->rt, nq)`** — удаляет все маршруты узла из таблицы
- Отменяет connectivity probes
- Удаляет paths, узел из `bgp->nodes`
- Закрывает все соединения etcp_router для этого узла
- **Broadcast WITHDRAW** дальше всем пирам (кроме отправителя)
#### `route_delete()` (`route_lib.c:221`) — что происходит внутри:
```c
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node)
```
Проходит по всему массиву entries, для каждой записи где `entry->v_node_info == node`:
- Сдвигает оставшиеся элементы влево через `memmove()`
- Декрементирует `count` и соответствующий счётчик статистики
- **Не освобождает** `NODEINFO_Q` (этим занимается вызывающий код в `route_bgp.c`)
### 2. Соединение ETCP упало (conn DOWN)
**Точка входа:** `route_bgp_remove_conn()` (`route_bgp.c:417`).
Алгоритм:
1. Отменяет pending NAT checks для линков этого соединения
2. Итерирует **все узлы** в `bgp->nodes`:
- Для каждого вызывает `route_bgp_remove_path(nq, conn)` — удаляет путь через этот conn
- Если путь был последним (unreachable):
- **`route_delete(rt, nq)`**
- Удаляет узел из `bgp->nodes`, отменяет пробы
- Закрывает etcp_router соединения
- **Broadcast WITHDRAW** с `wd_source = peer_node_id` (или свой node_id если удаляемый узел и есть сам пир)
3. Удаляет conn из `senders_list`
---
## Поиск маршрута и пересылка пакетов
### `route_lookup()` (`route_lib.c:248`) — LPM
Бинарный поиск по отсортированному массиву:
- Сравнивает `(dest_ip & mask) == (network & mask)`
- Запоминает лучший (наибольшая `prefix_length`)
- При совпадении продолжает поиск вправо (там могут быть более специфичные /32 внутри /24)
- Возвращает `ROUTE_ENTRY*` или NULL
### `route_pkt()` (`routing.c:53`) — единая точка пересылки
1. Извлекает IP-пакет (пропуская 1-байтовый cmd)
2. Дропает мультикаст (224-239) и броадкаст (255.255.255.255)
3. `route_lookup(instance->rt, dst_ip)` — ищет маршрут
4. **Нет маршрута** → дроп, `dropped_packets++`
5. **Маршрут удалённый** (`v_node_info != NULL && hop_count > 0`) → `etcp_route_send()` (через service router)
6. **Маршрут локальный** (нет v_node_info или hop_count==0) → `queue_data_put(instance->tun->input_queue)` (доставка в TUN)
Два входа в `route_pkt`:
- **Из TUN** (исходящий): `routing_pkt_from_tun_cb()` — callback на `tun->output_queue`
- **Из ETCP** (входящий): `routing_pkt_from_etcp_cb()` — callback через `etcp_router_bind(ETCP_RT_ID_DATA=0)`
---
## Loop prevention
Distance-vector стиль: каждый NODEINFO-пакет содержит **hop_list** — массив node_id, через которые он прошёл. Перед broadcast проверяется, что целевой пир **не находится** в hop_list. Аналогично при WITHDRAW — `wd_source` ищется в hop_list каждого path.
---
## NAT-детекция (влияет на обновление local_node)
1. При новом соединении сканируются все линки, для каждого вызывается `route_bgp_start_link_nat_check()`
2. Находится «третий узел» (другой подключённый пир, не private)
3. Через третий узел отправляется ping-запрос на целевой IP/порт (`route_ping_send_req_addr`)
4. Третий узел выполняет UDP-пинг, возвращает результат
5. По результату устанавливается `NAT_TYPE_EIM` (reachable) или `NAT_TYPE_STRICT`
6. Отправляется `NAT_INFO` обратно владельцу сокета
7. Владелец обновляет `local_node` → `route_bgp_update_my_nodeinfo()` → удаление/вставка локальных маршрутов → broadcast обновлённого NODEINFO
---
## Итого: кто когда вызывает route_insert/route_delete
| Событие | Операция | Функция |
|---|---|---|
| Получен новый/обновлённый NODEINFO | `route_insert` | `route_bgp_process_nodeinfo` `:849` |
| Обновление своего local_node | `route_delete` старого + `route_insert` нового | `route_bgp_update_my_nodeinfo` (`route_node.c`) |
| Получен WITHDRAW (узел недоступен) | `route_delete` | `route_bgp_process_withdraw` `:904` |
| Соединение упало (conn DOWN) | `route_delete` для каждого узла без путей | `route_bgp_remove_conn` `:468` |
| Переаллокация NODEINFO_Q при обновлении | `route_delete` старого | `route_bgp_process_nodeinfo` `:812` |
+71 -95
View File
@@ -1,4 +1,5 @@
bin_PROGRAMS = utun
noinst_LIBRARIES = libutun.a
# Sources that are always compiled
utun_CORE_SOURCES = \
@@ -8,10 +9,13 @@ utun_CORE_SOURCES = \
config_updater.c \
route_lib.c \
route6_lib.c \
route_bgp.c \
topo_group.c \
route_ping.c \
route_node.c \
topo_node.c \
topo_node_lmdb.c \
route_connectivity.c \
conn_mgr.c \
db_sync.c \
routing.c \
tun_if.c \
tun_route.c \
@@ -26,13 +30,16 @@ utun_CORE_SOURCES = \
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 \
etcp_connect.c \
control_server.c \
msg_transport.c \
firewall.c \
eim_nat.c \
nat_transport.c \
@@ -41,118 +48,87 @@ utun_CORE_SOURCES = \
etcp_router.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/socks_proxy.c \
proxy/icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \
lwip_tcp/lwip_tcp_out.c
# libutun: all core sources except main()
libutun_a_SOURCES = \
utun_instance.c \
config_parser.c \
config_updater.c \
route_lib.c \
route6_lib.c \
topo_group.c \
route_ping.c \
topo_node.c \
topo_node_lmdb.c \
route_connectivity.c \
conn_mgr.c \
db_sync.c \
routing.c \
tun_if.c \
tun_route.c \
tun_linux.c \
tun_freebsd.c \
tun_windows.c \
etcp.c \
etcp_connections.c \
etcp_bbr.c \
etcp_loadbalancer.c \
etcp_debug.c \
etcp_dump.c \
secure_channel.c \
crc32.c \
stcp_link.c \
stcp.c \
stcp_server.c \
stcp_client.c \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
etcp_connect.c \
control_server.c \
msg_transport.c \
firewall.c \
eim_nat.c \
nat_transport.c \
dummynet.c \
proxy/tcp_proxy_client.c \
etcp_router.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/socks_proxy.c \
proxy/icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \
lwip_tcp/lwip_tcp_out.c
libutun_a_CFLAGS = $(utun_CFLAGS)
# 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
utun_SOURCES = utun.c
# 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 = \
libutun.a \
$(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
+22
View File
@@ -0,0 +1,22 @@
Архитектура таблицы c быстрой репликацией между несколькими узлами:
1. В конец таблицы каждый узел может самостоятельно добавлять данные (со своим timestamp, желательно время правильное)
2. узлы между собой синхронизируются, распространяя обновления по соседям
3. TTL: изменение имеет время жизни. узел удаляет собственные записи которые не были отправлены никому в течении суток
4. Формат записи:
ID (64bit monotonic autoincrement)
chain_hash (256bit) - цепочка хешей. считается так: chain_hash[index+1]=hash(chain_hash[index],ID[index],timestamp[index],datahash[index]). если хеш совпадает - это признак того что все строки выше синхронизированы.
timestamp (64bit) - время создания записи. строки должны сортироваться по возрастанию concat(datahash(младшая часть числа,64 bit)+timestamp(старшая часть))
datahash (64bit) - хеш данных этой строки
data (varchar) - данные в формате json
алгоритм репликации (2 узла):
узел A запрашивает синхронизацию и передает свой last id
test_id= min(mast id, peer last id)
узел B передает: test_id, chain_hash(test id), hash(test id-1), hash(test id-2), hash(test id-4), hash(test id-8) итд 16,32,..., до первого элемента включитально (лимитируем вылетевший индекс первым элементом)
узел A - сравнивает, находит проверяемый диапазон id, отправляет другому узлу 16 (можно больше) хешей (линейно разбив проверяемый диапазон на более короткие поддиапазоны). таким образом узлы уточняют первый ид который не совпал.
Когда первая различающияся запись найдена, узел отправляет хеш этой и n (например 32) последующих записей (если записей много). если записей мало (<4) то узел отправляет сразу содержимое записей.
(надо додумать алгоритм, чтобы оптимизировать количество итераций - лучше передать больше данных за раз чем много итераций с ожиданием ответной стороны)
+9
View File
@@ -0,0 +1,9 @@
Задача сделать механизм маршрутизации между узлами для групповых чатов.
Узлы для всех чатов находятся в одной таблице - это nodeinfo (информация о подключении)
В другой таблице есть список узлов конкретного чата.
надо сделать возможность хранить одновременно несколько групп узлов. в группу узлов добавить тип - utun или чатовая.
если utun - в группе используется роутинг (ipv4/ipv6).
если чатовая - то только связность между узлами.
+141 -14
View File
@@ -46,7 +46,9 @@ typedef enum {
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER
SECTION_TCP_PROXY_SERVER,
SECTION_MSG_TRANSPORT,
SECTION_NETWORK
} section_type_t;
static char* trim(char *str) {
@@ -356,6 +358,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->my_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "tun_enabled") == 0) {
global->tun_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "tun_ifname") == 0) {
snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value);
return 0;
@@ -376,6 +382,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "keepalive_adaptive") == 0) {
global->keepalive_adaptive = atoi(value);
return 0;
}
if (strcmp(key, "bbr_max_cwnd") == 0) {
global->bbr_max_cwnd = atoi(value);
return 0;
@@ -386,6 +396,17 @@ 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);
}
if (strcmp(key, "db_sync_enabled") == 0) {
global->db_sync_enabled = atoi(value);
return 0;
}
if (strcmp(key, "db_sync_ttl") == 0) {
global->db_sync_ttl = (uint32_t)atoi(value);
return 0;
}
/* debug_categories key is deprecated - use [debug] section instead */
if (strcmp(key, "enable_timestamp") == 0) {
global->enable_timestamp = atoi(value);
@@ -407,7 +428,7 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, keepalive_adaptive, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1;
}
@@ -436,6 +457,17 @@ static int parse_control(const char *key, const char *value, struct global_confi
return -1;
}
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_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
@@ -457,6 +489,22 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "socks_enabled") == 0) {
global->tcp_proxy_client_socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "socks_addr") == 0) {
strncpy(global->tcp_proxy_client_socks_addr, value, sizeof(global->tcp_proxy_client_socks_addr) - 1);
return 0;
}
if (strcmp(key, "http_proxy_enabled") == 0) {
global->tcp_proxy_client_http_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "http_proxy_addr") == 0) {
strncpy(global->tcp_proxy_client_http_proxy_addr, value, sizeof(global->tcp_proxy_client_http_proxy_addr) - 1);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
@@ -493,7 +541,7 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
global->tcp_proxy_client_mapping_count++;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward, socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr", filename, line_num, key);
return -1;
}
@@ -611,6 +659,8 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->type = CFG_SERVER_TYPE_NAT;
} else if (strcmp(value, "private") == 0) {
srv->type = CFG_SERVER_TYPE_PRIVATE;
} else if (strcmp(value, "local") == 0) {
srv->type = CFG_SERVER_TYPE_LOCAL;
} else {
srv->type = CFG_SERVER_TYPE_UNKNOWN;
}
@@ -624,7 +674,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;
}
@@ -670,6 +725,21 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
return -1;
}
static int parse_network(const char *key, const char *value, struct CFG_NETWORK *net, const char *filename, int line_num) {
if (strcmp(key, "id") == 0) {
net->id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "pubkey") == 0) {
return assign_string(net->pubkey_hex, sizeof(net->pubkey_hex), value);
}
if (strcmp(key, "signing_key") == 0) {
return assign_string(net->signing_key_hex, sizeof(net->signing_key_hex), value);
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown network option '%s'. Valid: id, pubkey, signing_key", filename, line_num, key);
return -1;
}
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
if (line[0] != '[') return SECTION_UNKNOWN;
@@ -692,6 +762,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -705,14 +776,15 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(type, "server") == 0) return SECTION_SERVER;
if (strcasecmp(type, "client") == 0) return SECTION_CLIENT;
if (strcasecmp(type, "network") == 0) return SECTION_NETWORK;
return SECTION_UNKNOWN;
}
static struct utun_config* parse_config_internal(FILE *fp, const char *filename) {
struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config));
if (!cfg) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for config structure");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for config structure");
return NULL;
}
@@ -720,6 +792,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
cfg->global.tcp_recv_buf = 0; // 0 = OS default
cfg->global.firewall_rules = NULL;
@@ -727,10 +800,13 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.firewall_bypass_all = 0;
cfg->global.control_allows = NULL;
cfg->global.control_allow_count = 0;
cfg->global.db_sync_enabled = 0;
cfg->global.db_sync_ttl = 86400;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
struct CFG_CLIENT *cur_client = NULL;
struct CFG_NETWORK *cur_network = NULL;
char line[MAX_LINE_LEN];
int line_num = 0;
@@ -754,6 +830,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->clients = cur_client;
cur_client = NULL;
}
if (cur_section == SECTION_NETWORK && cur_network) {
cur_network->next = cfg->networks;
cfg->networks = cur_network;
cur_network = NULL;
}
char name[MAX_CONN_NAME_LEN];
cur_section = parse_section_header(trimmed, name, sizeof(name));
@@ -761,7 +842,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
if (cur_section == SECTION_SERVER) {
cur_server = u_calloc(1, sizeof(struct CFG_SERVER));
if (!cur_server) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for server %s", name);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for server %s", name);
goto error;
}
strcpy(cur_server->name, name);
@@ -769,10 +850,17 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
} else if (cur_section == SECTION_CLIENT) {
cur_client = u_calloc(1, sizeof(struct CFG_CLIENT));
if (!cur_client) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for client %s", name);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for client %s", name);
goto error;
}
strcpy(cur_client->name, name);
} else if (cur_section == SECTION_NETWORK) {
cur_network = u_calloc(1, sizeof(struct CFG_NETWORK));
if (!cur_network) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for network %s", name);
goto error;
}
strcpy(cur_network->name, name);
}
continue;
}
@@ -874,6 +962,16 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
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;
case SECTION_NETWORK:
if (cur_network && parse_network(key, value, cur_network, filename, line_num) < 0) {
// parse_network already printed the error
}
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@@ -889,11 +987,23 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cur_client->next = cfg->clients;
cfg->clients = cur_client;
}
if (cur_section == SECTION_NETWORK && cur_network) {
cur_network->next = cfg->networks;
cfg->networks = cur_network;
}
// Set default TUN interface name if not provided
if (cfg->global.tun_ifname[0] == '\0') {
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
// Post-parse defaults: tun_enabled=1 if not explicitly disabled
if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {
// No TUN config at all — treat as explicitly off (for non-root SOCKS/HTTP-only mode)
} else if (!cfg->global.tun_enabled) {
// TUN was explicitly disabled via tun_enabled=no
} else {
// TUN enabled (default) — set default ifname if not provided
cfg->global.tun_enabled = 1;
if (cfg->global.tun_ifname[0] == '\0') {
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
}
}
return cfg;
@@ -904,6 +1014,7 @@ error:
free_cfg_client_links(cur_client->links);
u_free(cur_client);
}
if (cur_network) u_free(cur_network);
free_config(cfg);
return NULL;
}
@@ -942,6 +1053,14 @@ void free_config(struct utun_config *config) {
client = next;
}
// Free networks
struct CFG_NETWORK *network = config->networks;
while (network) {
struct CFG_NETWORK *next = network->next;
u_free(network);
network = next;
}
// Free route entries
free_route_entries(config->route_subnets);
free_route_entries(config->my_subnets);
@@ -1036,8 +1155,16 @@ void print_config(const struct utun_config *cfg) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s", ip_str);
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s:%d", ip_str, r->port);
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks:");
struct CFG_NETWORK *net = cfg->networks;
while (net) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " [network:%s] id=%012llx pubkey=%.64s signing_key=%.64s",
net->name, (unsigned long long)net->id, net->pubkey_hex, net->signing_key_hex);
net = net->next;
}
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
}
+24 -3
View File
@@ -24,9 +24,10 @@ struct IP {
};
#define CFG_SERVER_TYPE_UNKNOWN 0
#define CFG_SERVER_TYPE_PUBLIC 1
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PUBLIC 1
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PRIVATE 3
#define CFG_SERVER_TYPE_LOCAL 4
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
@@ -40,6 +41,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
@@ -60,6 +62,14 @@ struct CFG_CLIENT {
struct CFG_CLIENT *next; // Next client in linked list
};
struct CFG_NETWORK {
struct CFG_NETWORK* next;
char name[64];
uint64_t id; // 56-bit network ID
char pubkey_hex[SC_PUBKEY_SIZE * 2 + 1]; // 64 hex chars
char signing_key_hex[SC_PRIVKEY_SIZE * 2 + 1]; // 64 hex chars
};
struct CFG_ROUTE_ENTRY {
struct CFG_ROUTE_ENTRY* next;
struct IP ip;
@@ -78,7 +88,7 @@ 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;
};
@@ -94,14 +104,19 @@ struct global_config {
char my_private_key_hex[MAX_KEY_LEN];
char my_public_key_hex[MAX_KEY_LEN];
uint64_t my_node_id;
int tun_enabled; // 1 = create TUN (default), 0 = skip TUN (run without root)
char tun_ifname[16]; // TUN interface name (e.g., "tun12")
struct IP tun_ip;
int mtu;
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)
int db_sync_enabled; // 1 = enable distributed DB sync (default: 0)
uint32_t db_sync_ttl; // TTL for unsent records in seconds (default: 86400)
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,6 +133,7 @@ struct global_config {
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200)
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
// Firewall configuration
@@ -158,6 +174,10 @@ struct global_config {
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
int tcp_proxy_client_mapping_count;
int tcp_proxy_client_socks_enabled;
char tcp_proxy_client_socks_addr[64]; // e.g. "127.0.0.1:1080"
int tcp_proxy_client_http_proxy_enabled;
char tcp_proxy_client_http_proxy_addr[64]; // e.g. "127.0.0.1:8080"
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
@@ -170,6 +190,7 @@ struct utun_config {
struct CFG_CLIENT* clients;
struct CFG_ROUTE_ENTRY* route_subnets;
struct CFG_ROUTE_ENTRY* my_subnets;
struct CFG_NETWORK* networks;
};
struct utun_config* parse_config(const char *filename);
+41 -26
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;
@@ -151,21 +151,25 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
char *opt_pos = find_option(*buf, *buf_len, option);
if (opt_pos) {
// Find option line end
char *line_end = strchr(opt_pos, '\n');
// Determine if old line has trailing \n
char *nl = strchr(opt_pos, '\n');
int has_trailing_nl = (nl != NULL);
char *line_end = nl ? nl : *buf + *buf_len;
if (!line_end) line_end = *buf + *buf_len;
// Prepare new line
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
int new_len;
if (has_trailing_nl) {
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
} else {
new_len = snprintf(new_line, sizeof(new_line), "%s=%s", option, value);
}
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
// Calculate length difference
size_t old_line_len = line_end - opt_pos + 1;
size_t old_line_len = has_trailing_nl ? (line_end - opt_pos + 1) : (line_end - opt_pos);
long len_diff = new_len - old_line_len;
// Ensure buffer capacity
if (*buf_len + len_diff + 1 > *buf_capacity) {
*buf_capacity = *buf_len + len_diff + 1024;
@@ -177,11 +181,11 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
opt_pos = find_option(*buf, *buf_len, option);
if (!opt_pos) return -1;
line_end = strchr(opt_pos, '\n');
if (!line_end) line_end = *buf + *buf_len;
nl = strchr(opt_pos, '\n');
has_trailing_nl = (nl != NULL);
line_end = nl ? nl : *buf + *buf_len;
}
// Move content and insert new line
memmove(opt_pos + new_len, line_end, *buf_len - (line_end - *buf) + 1);
@@ -194,24 +198,31 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
char *section_end = find_section_global(*buf, *buf_len);
if (!section_end) return -1;
// Find end of global section (next [ or end of buffer)
char *insert_pos = strchr(section_end, '[');
int need_leading_nl = 0;
if (!insert_pos) insert_pos = *buf + *buf_len;
else {
if (!insert_pos) {
insert_pos = *buf + *buf_len;
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
need_leading_nl = 1;
} else {
// Move to beginning of next line
char *prev_nl = insert_pos;
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
}
// Prepare new line
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
int new_len;
if (need_leading_nl)
new_len = snprintf(new_line, sizeof(new_line), "\n%s=%s\n", option, value);
else
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
// Ensure buffer capacity
if (*buf_len + new_len + 1 > *buf_capacity) {
*buf_capacity = *buf_len + new_len + 1024;
@@ -224,15 +235,19 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
if (!section_end) return -1;
insert_pos = strchr(section_end, '[');
need_leading_nl = 0;
if (!insert_pos) insert_pos = *buf + *buf_len;
else {
if (!insert_pos) {
insert_pos = *buf + *buf_len;
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
need_leading_nl = 1;
} else {
char *prev_nl = insert_pos;
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
}
}
// Move content and insert new line
memmove(insert_pos + new_len, insert_pos, *buf_len - (insert_pos - *buf) + 1);
+1010
View File
File diff suppressed because it is too large. Load diff
+448
View File
@@ -0,0 +1,448 @@
#ifndef CONN_MGR_H
#define CONN_MGR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "topo_node.h"
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct TOPO_NODEQ;
struct TOPO_GROUP;
/**
* @file conn_mgr.h
* @brief Менеджер установки соединений. Трёхфазная машина подключения к нодам
* с выбором оптимального пути: прямой → обратный → через посредников.
*
* Основные возможности:
* - Автоматический выбор лучшего способа подключения к целевой ноде
* - Фаза 1 (DIRECT): попытка прямого INIT-рукопожатия на все известные IPv4-адреса цели.
* Итерация по парам (наш сокет, целевой адрес) с проверкой NAT-совместимости.
* Таймаут: CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS
* - Фаза 2 (REVERSE): если у нас есть прямой IP, а у цели нет — отправляем DIRECT_REQ
* с нашими адресами через BGP-маршрут. Цель сама инициирует INIT в нашу сторону.
* Таймаут: CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS
* - Фаза 3 (INDIRECT): если DIRECT и REVERSE не сработали — обмениваемся через BGP
* списками лучших промежуточных узлов-кандидатов с RTT, выбираем общих с минимальным
* суммарным RTT. Трафик идёт через etcp_router (транзитная ретрансляция).
* Таймаут: CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS
* - Фоновое сканирование локальной сети (broadcast/multicast UDP-ping)
* - Фоновое поддержание списка лучших кандидатов-посредников (периодический ping,
* удаление stale-кандидатов)
* - Фоновый обход всех известных BGP-нод для поддержания RTT-замеров
* - Управление idle-таймаутом с автоматическим разрывом неактивных соединений
*
* @note Правила использования:
* 1. conn_mgr_init() вызывается один раз при старте экземпляра utun
* 2. conn_mgr_connect_node() — асинхронный вызов, результат приходит в callback
* 3. Повторный вызов conn_mgr_connect_node для уже подключённой ноды вернёт
* CONN_MGR_ERR_ALREADY_CONNECTED; для ноды в состоянии CONNECTING добавит
* дополнительный callback в список
* 4. conn_mgr_disconnect_node() отправляет DISCONNECT-пакет целевой ноде и
* сбрасывает conn_mgr_type в NODEINFO_Q
* 5. Для alien-нод (подключившихся к нам самостоятельно) используется
* conn_mgr_add_alien_node() вместо conn_mgr_connect_node()
* 6. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity)
* для обновления RTT кандидатов
*
* @note Внутренние таймеры (всё в timebase-единицах, 1 tb = 0.1 мс):
* - bg_ping_timer: фоновый ping нод (CONN_MGR_BG_PING_INTERVAL_TB)
* - candidate_ping_timer: обновление RTT кандидатов (CONN_MGR_CANDIDATE_PING_TB)
* - idle_timer: проверка неактивности подключённых нод (CONN_MGR_IDLE_CHECK_INTERVAL_TB)
*/
/* ==================== Константы и лимиты ==================== */
#define CONN_MGR_MAX_CANDIDATES 3 ///< Макс. число лучших кандидатов-посредников
#define CONN_MGR_CANDIDATE_CACHE_MS 20000 ///< Время жизни кандидата в кеше (мс) — не используется напрямую в .h
#define CONN_MGR_CANDIDATE_STALE_TB 300000 ///< Порог устаревания кандидата (tb), после которого удаляется из списка
#define CONN_MGR_CANDIDATE_PING_TB 20000 ///< Интервал фонового ping кандидатов (tb, ~2 сек)
#define CONN_MGR_BG_PING_INTERVAL_TB 1000 ///< Интервал фонового ping BGP-нод (tb, ~100 мс)
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 ///< Минимальная длительность полного цикла обхода нод (tb, ~10 сек)
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 ///< Интервал проверки idle-таймаута (tb, ~1 сек)
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 ///< Число попыток локального сканирования
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 ///< Таймаут локального сканирования (мс)
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000 ///< Таймаут фазы DIRECT (мс)
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 ///< Таймаут фазы REVERSE (мс)
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 ///< Таймаут фазы INDIRECT (мс)
/* ==================== Коды возврата ==================== */
#define CONN_MGR_OK 0 ///< Успех
#define CONN_MGR_ERR_NOT_FOUND -1 ///< Нода не найдена в BGP
#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
* @brief Состояние подключения к ноде.
*/
enum CONN_MGR_STATE {
CONN_MGR_STATE_DISCONNECTED = 0, ///< Не подключены
CONN_MGR_STATE_CONNECTING = 1, ///< В процессе установки соединения
CONN_MGR_STATE_CONNECTED = 2, ///< Подключены
};
/**
* @enum CM_TRY_STATE
* @brief Состояние отдельной попытки (локальное сканирование / основное подключение).
*/
enum CM_TRY_STATE {
CM_TRY_NONE = 0, ///< Попытка не начиналась
CM_TRY_PENDING = 1, ///< Попытка в процессе
CM_TRY_OK = 2, ///< Попытка успешна
CM_TRY_FAILED = 3, ///< Попытка провалилась
};
/* ==================== Протокол CONN_MGR (ETCP_RT_ID_CONN_MGR = 0x11) ==================== */
/**
* @defgroup conn_mgr_subcmd Подкоманды протокола CONN_MGR
* @{
*/
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01 ///< "Я за NAT, подключись ко мне" — передача наших адресов цели
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02 ///< Ответ на DIRECT_REQ (зарезервирован, не реализован)
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03 ///< Запрос обмена кандидатами-посредниками
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04 ///< Ответ с нашими кандидатами + замеры RTT до кандидатов инициатора
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05 ///< Выбранные посредники (финальный шаг INDIRECT-фазы)
#define CONN_MGR_SUBCMD_DISCONNECT 0x06 ///< Уведомление о разрыве соединения
/** @} */
/**
* @struct CONN_MGR_CANDIDATE
* @brief Запись о кандидате-посреднике: идентификатор ноды + RTT до неё.
*
* Используется в списке best_candidates и во всех пакетах обмена кандидатами.
*/
struct CONN_MGR_CANDIDATE {
uint64_t node_id; ///< Идентификатор ноды-кандидата
uint16_t rtt; ///< RTT до кандидата (мс)
} __attribute__((packed));
#pragma pack(push, 1)
/**
* @struct CONN_MGR_DIRECT_REQ
* @brief Пакет фазы REVERSE: "подключись ко мне".
*
* Отправляется когда мы имеем прямой IP, но цель за NAT.
* Содержит наши адреса, по которым цель должна инициировать INIT.
*/
struct CONN_MGR_DIRECT_REQ {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_REQ
uint32_t request_id; ///< Идентификатор запроса для сопоставления ответа
uint8_t addr_count; ///< Количество адресов, следующих за заголовком
/* далее: addr_count записей по 8 байт каждая (type:1, ip:4, port:2, sock_id:1) */
};
/**
* @struct CONN_MGR_DIRECT_RESP
* @brief Ответ на DIRECT_REQ (зарезервирован, не отправляется).
*/
struct CONN_MGR_DIRECT_RESP {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_RESP
uint32_t request_id; ///< Идентификатор запроса
uint8_t accepted; ///< 1 — принято, 0 — отклонено
};
/**
* @struct CONN_MGR_INTERM_EXCHANGE_REQ
* @brief Запрос обмена списками кандидатов-посредников (фаза INDIRECT, инициатор).
*
* Инициатор отправляет свой топ-4 кандидатов. Цель должна ответить
* CONN_MGR_INTERM_EXCHANGE_RESP со своими кандидатами и замерами RTT до наших.
*/
struct CONN_MGR_INTERM_EXCHANGE_REQ {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ
uint32_t request_id; ///< Идентификатор запроса
uint8_t candidate_count; ///< Количество кандидатов в массиве (≤4)
struct CONN_MGR_CANDIDATE candidates[4]; ///< Наши лучшие кандидаты с RTT
};
/**
* @struct CONN_MGR_INTERM_EXCHANGE_RESP
* @brief Ответ на INTERM_EXCHANGE_REQ (фаза INDIRECT, цель).
*
* Содержит два списка:
* - my_candidates: лучшие кандидаты цели с RTT до них (измеренным целью)
* - your_candidates: кандидаты инициатора, но с RTT, измеренным со стороны цели
*/
struct CONN_MGR_INTERM_EXCHANGE_RESP {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP
uint32_t request_id; ///< Идентификатор запроса
uint8_t my_count; ///< Количество наших кандидатов (≤4)
uint8_t your_count; ///< Количество кандидатов инициатора (≤4)
struct CONN_MGR_CANDIDATE my_candidates[4]; ///< Наши кандидаты + наш RTT до них
struct CONN_MGR_CANDIDATE your_candidates[4]; ///< Кандидаты инициатора + наш RTT до них
};
/**
* @struct CONN_MGR_INTERM_SELECTED
* @brief Финальный выбор посредников (фаза INDIRECT).
*
* Инициатор вычисляет лучших общих посредников по сумме RTT
* и отправляет выбранный список цели.
*/
struct CONN_MGR_INTERM_SELECTED {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_SELECTED
uint32_t request_id; ///< Идентификатор запроса (всегда 0 для извещения)
uint8_t count; ///< Количество выбранных посредников (≤3)
struct CONN_MGR_CANDIDATE selected[3]; ///< Выбранные посредники (node_id + RTT от нас до них)
};
/**
* @struct CONN_MGR_DISCONNECT
* @brief Уведомление о разрыве соединения.
*/
struct CONN_MGR_DISCONNECT {
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DISCONNECT
uint64_t node_id; ///< Идентификатор ноды, с которой разрываем соединение
};
#pragma pack(pop)
/* ==================== Размеры пакетов протокола ==================== */
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ) ///< Размер заголовка DIRECT_REQ (без addr_count адресов)
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP) ///< Размер DIRECT_RESP
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ) ///< Размер INTERM_EXCHANGE_REQ
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP) ///< Размер INTERM_EXCHANGE_RESP (макс.)
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED) ///< Размер INTERM_SELECTED
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT) ///< Размер DISCONNECT
/* ==================== Callback-типы и внутренние структуры ==================== */
/**
* @typedef conn_mgr_connect_callback_t
* @brief Callback результата подключения.
* @param result код возврата (CONN_MGR_OK или CONN_MGR_ERR_*)
* @param node_id идентификатор ноды
* @param arg пользовательский аргумент
*/
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
/**
* @struct cm_cb_node
* @brief Узел связного списка callback'ов для одной ноды.
*
* Позволяет регистрировать несколько callback'ов на одно подключение.
* Новые callback'и добавляются в начало списка.
*/
struct cm_cb_node {
conn_mgr_connect_callback_t cb; ///< Функция-callback
void* arg; ///< Пользовательский аргумент
struct cm_cb_node* next; ///< Следующий callback в списке
};
/**
* @struct CONN_MGR_ENTRY
* @brief Запись о подключении к одной ноде.
*
* Хранит состояние трёхфазной машины, список выбранных посредников,
* idle-таймер и список ожидающих callback'ов.
*/
struct CONN_MGR_ENTRY {
uint64_t node_id; ///< Идентификатор целевой ноды
uint8_t state; ///< Текущее состояние (enum CONN_MGR_STATE)
uint8_t conn_type; ///< Тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT)
uint8_t alien; ///< 1 — alien-нода (сама подключилась к нам), 0 — мы инициаторы
uint32_t idle_timeout_ms; ///< Таймаут неактивности (мс), 0 — без таймаута
uint64_t last_traffic_tb; ///< Время последнего трафика (tb) для idle-проверки
void* idle_timer; ///< Хендл таймера idle-проверки
struct cm_cb_node* cb_list; ///< Связный список callback'ов результата
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; ///< Выбранные посредники (node_id)
uint8_t intermediariy_count; ///< Количество выбранных посредников
uint8_t rr_idx; ///< Round-robin индекс для выбора посредника при отправке
uint8_t local_scan_state; ///< Состояние локального сканирования (enum CM_TRY_STATE)
uint8_t main_connect_state; ///< Состояние основного подключения (enum CM_TRY_STATE)
struct {
uint8_t phase; ///< Текущая фаза: 1=DIRECT, 2=REVERSE, 3=INDIRECT
void* timer; ///< Хендл таймера текущей фазы
uint32_t request_id; ///< request_id для INDIRECT-обмена
void* conn_ctx; ///< Контекст ETCP-соединения (для REVERSE)
} main;
struct CONN_MGR* mgr; ///< Обратная ссылка на менеджер
};
/**
* @struct CONN_MGR
* @brief Корневая структура менеджера соединений.
*
* Создаётся один раз в conn_mgr_init(), хранится в UTUN_INSTANCE.
* Содержит динамический массив entries, список лучших кандидатов,
* таймеры фонового ping и список ожидающих обменов (reverse/exchange pending).
*/
struct CONN_MGR {
struct UTUN_INSTANCE* instance; ///< Родительский экземпляр utun
struct CONN_MGR_ENTRY* entries; ///< Динамический массив записей о подключениях
size_t entry_count; ///< Текущее количество записей
size_t entry_capacity; ///< Выделенная ёмкость массива entries
void* bg_ping_timer; ///< Хендл таймера фонового ping BGP-нод
size_t bg_ping_cursor; ///< Курсор текущей ноды в цикле обхода
uint64_t bg_ping_cycle_start_tb; ///< Время старта текущего цикла обхода (tb)
void* candidate_ping_timer; ///< Хендл таймера обновления RTT кандидатов
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; ///< Топ кандидатов-посредников, отсортированных по RTT
uint8_t best_candidate_count; ///< Количество актуальных кандидатов (≤CONN_MGR_MAX_CANDIDATES)
uint32_t next_request_id; ///< Счётчик request_id для INTERM_EXCHANGE
uint8_t initialized; ///< Флаг инициализации
struct cm_reverse_pending* reverse_pending; ///< Связный список ожидающих REVERSE-подключений
struct cm_exchange_pending* exchange_pending; ///< Связный список ожидающих INDIRECT-обменов
};
/* ==================== Жизненный цикл ==================== */
/**
* @brief Инициализирует менеджер соединений.
* @param instance родительский экземпляр utun
* @return указатель на CONN_MGR или NULL при ошибке
*
* Регистрирует обработчик в etcp_router для ETCP_RT_ID_CONN_MGR,
* запускает фоновые таймеры (bg_ping, candidate_ping).
*/
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
/**
* @brief Уничтожает менеджер соединений.
* @param mgr менеджер
*
* Освобождает все записи, таймеры, pending-структуры.
* Вызывает cm_clear_nodeinfo для каждой записи.
*/
void conn_mgr_destroy(struct CONN_MGR* mgr);
/* ==================== Подключение / отключение ==================== */
/**
* @brief Запускает асинхронное подключение к ноде.
* @param mgr менеджер
* @param node_id идентификатор целевой ноды
* @param idle_timeout_ms таймаут неактивности (мс), 0 = без таймаута
* @param cb callback результата (CONN_MGR_OK, CONN_MGR_ERR_TIMEOUT, CONN_MGR_ERR_UNREACHABLE и т.д.)
* @param cb_arg пользовательский аргумент для callback
* @return CONN_MGR_OK если процесс запущен, CONN_MGR_ERR_* при ошибке
*
* @note Если нода уже подключена — сразу вызывает callback с CONN_MGR_OK и возвращает CONN_MGR_OK.
* @note Если нода в процессе подключения — добавляет callback в список ожидания.
* @note Автоматически определяет наличие BGP-соединения (уже существующий ETCP-линк).
* @note Для alien-нод используйте conn_mgr_add_alien_node().
*/
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);
/**
* @brief Разрывает подключение к ноде.
* @param mgr менеджер
* @param node_id идентификатор ноды
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND
*
* Отправляет DISCONNECT-пакет целевой ноде, сбрасывает conn_mgr_type в NODEINFO_Q,
* уничтожает запись entry.
*/
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
/* ==================== Управление idle-таймаутом ==================== */
/**
* @brief Устанавливает idle-таймаут для подключённой ноды.
* @param mgr менеджер
* @param node_id идентификатор ноды
* @param timeout_ms таймаут неактивности (мс), 0 = отключить проверку
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND / CONN_MGR_ERR_INTERNAL
*
* Если таймаут > 0 и нода в состоянии CONNECTED — запускает периодический таймер.
* При отсутствии трафика в течение timeout_ms соединение автоматически разрывается.
*/
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
/**
* @brief Получает текущий статус подключения к ноде.
* @param mgr менеджер
* @param node_id идентификатор ноды
* @param out_state [out] состояние (enum CONN_MGR_STATE)
* @param out_conn_type [out] тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT)
* @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);
/* ==================== Alien-ноды ==================== */
/**
* @brief Регистрирует alien-ноду (подключившуюся к нам самостоятельно).
* @param mgr менеджер
* @param nodeinfo_data сырые данные NODEINFO (BGP-пакет)
* @param len длина данных
* @return CONN_MGR_OK или CONN_MGR_ERR_INTERNAL
*
* Alien-нода — это нода, которая сама инициировала INIT-подключение к нам.
* Для неё не запускаются фазы подключения, сразу устанавливается CONN_MGR_STATE_CONNECTED.
* Создаёт entry с флагом alien=1.
*/
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
/**
* @brief Отправляет данные подключённой ноде через etcp_router.
* @param mgr менеджер
* @param node_id идентификатор целевой ноды
* @param entry ll_entry с данными для отправки (освобождается после отправки)
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND
*
* Для INDIRECT-соединений пакет автоматически маршрутизируется через
* выбранного посредника (round-robin по intermediaries[]).
* Обновляет last_traffic_tb для idle-проверки.
*/
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
/* ==================== Управление кандидатами ==================== */
/**
* @brief Обновляет RTT кандидата в списке лучших посредников.
* @param mgr менеджер
* @param node_id идентификатор ноды-кандидата
* @param rtt RTT до кандидата (мс)
*
* Если кандидат уже в списке — обновляет RTT. Если нет — вставляет с сортировкой
* по возрастанию RTT. Если список заполнен и новый кандидат имеет худший RTT —
* игнорируется. Список всегда отсортирован: best_candidates[0] — наименьший RTT.
*
* @note Обычно вызывается из route_connectivity после успешного probe.
*/
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
#ifdef __cplusplus
}
#endif
#endif
+277 -53
View File
@@ -10,7 +10,9 @@
#include "etcp_connections.h"
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "topo_group.h"
#include "etcp_router.h"
#include "topo_node.h"
#include "pkt_normalizer.h"
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/u_async.h"
@@ -89,6 +91,7 @@ static void client_write_callback(socket_t fd, void* arg);
static void client_except_callback(socket_t fd, void* arg);
static void handle_client_data(struct control_server* server, struct control_client* client);
static void close_client(struct control_server* server, struct control_client* client);
static void control_client_idle_cb(void* arg);
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
@@ -96,8 +99,16 @@ static void send_metrics(struct control_server* server, struct control_client* c
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 TOPO_NODEQ* 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
* ============================================================================ */
@@ -400,13 +411,27 @@ 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;
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
/* Register with uasync */
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
client_read_callback,
NULL, /* write callback - not needed for now */
client_write_callback,
client_except_callback,
client);
@@ -454,6 +479,12 @@ static void client_read_callback(socket_t fd, void* arg) {
struct control_client* client = (struct control_client*)arg;
struct control_server* server = client->server;
if (client->recv_len >= ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow (pre-check)");
close_client(server, client);
return;
}
/* Read available data */
uint8_t* buf = client->recv_buffer + client->recv_len;
size_t buf_space = ETCPMON_MAX_MSG_SIZE - client->recv_len;
@@ -544,9 +575,45 @@ 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;
if (q->count > 128) DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "ctrl_out: large output queue count=%d fd=%d", q->count, (int)client->fd);
(void)fd;
(void)arg;
}
static void client_except_callback(socket_t fd, void* arg) {
@@ -560,10 +627,35 @@ 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 control_client_idle_cb(void* arg) {
struct control_client* client = (struct control_client*)arg;
if (!client || !client->server) return;
client->idle_timer = NULL;
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control client idle timeout fd=%d, closing", client->fd);
close_client(client->server, client);
}
static void close_client(struct control_server* server, struct control_client* client) {
if (!server || !client) return;
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); client->idle_timer = NULL; }
/* Log disconnection */
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client connection closed\n",
@@ -583,7 +675,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) {
@@ -614,7 +708,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_SET_DEBUG_CONFIG) {
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;
@@ -690,9 +784,30 @@ static void handle_client_data(struct control_server* server, struct control_cli
struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload;
if (strncmp(cmd->action, "nat", 3) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
route_bgp_request_nat_check_all(server->instance->bgp);
topo_group_request_nat_check_all(topo_groups_get_default(server->instance->topo_groups));
} else if (strncmp(cmd->action, "nodes", 5) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes");
uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024;
char* text = (char*)u_malloc(text_max);
if (text) {
int text_len = topo_node_format_all(topo_groups_get_default(server->instance->topo_groups), text, text_max);
if (text_len < 0) text_len = 0;
text[text_len] = '\0';
uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (buffer) {
struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1,
ETCPMON_RSP_ACTION_RESULT, req_seq);
struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr));
rsp->code = 0;
memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1);
control_client_send(server, client, buffer, rsp_size);
}
u_free(text);
}
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%.32s'", cmd->action);
}
}
break;
@@ -721,6 +836,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
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",
@@ -736,6 +862,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
return;
}
/* Reset idle timer on each valid command */
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); }
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
/* Remove processed message from buffer */
uint16_t msg_size = hdr->size;
if (client->recv_len > msg_size) {
@@ -795,13 +925,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) {
@@ -819,8 +943,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;
}
@@ -868,6 +991,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
info[i].port = 0;
}
info[i].config_type = sock->type;
info[i].nat_type = sock->nat_type;
/* NAT address from socket (set when INIT_RESPONSE received) */
@@ -882,24 +1006,21 @@ static void send_socket_list(struct control_server* server, struct control_clien
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin6->sin6_port);
}
DEBUG_INFO(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%d nat_type=%d nat_ip=%s nat_port=%u",
sock->name, sock->type, sock->nat_type,
info[i].nat_ip[0] ? info[i].nat_ip : "none", info[i].nat_port);
sock = sock->next;
}
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) {
@@ -1067,17 +1188,56 @@ static void send_metrics(struct control_server* server, struct control_client* c
}
/* BGP routing */
if (instance->bgp) {
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
{ struct TOPO_GROUP* g = topo_groups_get_default(instance->topo_groups);
if (g) {
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(g->senders_list);
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(g->nodes);
} else {
rsp->tun.rt_bgp_senders = 0;
rsp->tun.rt_bgp_nodes = 0;
}
} }
} else {
memset(&rsp->tun, 0, sizeof(rsp->tun));
}
/* Fill router congestion metrics — aggregate over all router_conns */
memset(&rsp->router, 0, sizeof(rsp->router));
if (instance->router_conns) {
uint32_t rtt_cnt = 0;
struct ll_entry* re = instance->router_conns->head;
while (re) {
struct ETCP_ROUTER_CONN* rc = (struct ETCP_ROUTER_CONN*)re;
rsp->router.total_inflight += (uint32_t)(rc->tx_seq - rc->tx_acked);
if (rc->send_q) rsp->router.total_send_q += (uint32_t)queue_entry_count(rc->send_q);
if (rc->recv_q) rsp->router.total_recv_q += (uint32_t)queue_entry_count(rc->recv_q);
rsp->router.pkts_sent += rc->c_pkts_sent;
rsp->router.pkts_send_err += rc->c_pkts_send_err;
rsp->router.pkts_rcvd += rc->c_pkts_rcvd;
rsp->router.ack_sent += rc->c_ack_sent;
rsp->router.ack_recv += rc->c_ack_recv;
rsp->router.dup_dropped += rc->c_dup_dropped;
rsp->router.oob_dropped += rc->c_oob_dropped;
rsp->router.stale_ack += rc->c_stale_ack;
rsp->router.sign_fail += rc->c_sign_fail;
if (rc->rtt > 0) {
if (rc->rtt > rsp->router.rtt_last) rsp->router.rtt_last = rc->rtt;
rsp->router.rtt_avg += rc->rtt;
rtt_cnt++;
}
{
uint16_t j = (uint16_t)(rc->rtt_jitter / 32768);
if (j > rsp->router.jitter) rsp->router.jitter = j;
}
if (rc->minrtt_window_count > 0) {
if (rsp->router.minrtt == 0 || rc->minrtt < rsp->router.minrtt)
rsp->router.minrtt = rc->minrtt;
}
re = re->next;
}
if (rtt_cnt > 0) rsp->router.rtt_avg /= rtt_cnt;
rsp->router.total_conns = (uint32_t)queue_entry_count(instance->router_conns);
}
/* Fill link metrics */
struct etcpmon_link_metrics* link_info = (struct etcpmon_link_metrics*)(buffer + sizeof(*hdr) + sizeof(*rsp));
link = conn->links;
@@ -1136,13 +1296,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) {
@@ -1170,13 +1324,7 @@ 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
u_free(buffer);
control_client_send(server, client, buffer, rsp_size);
}
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
@@ -1215,13 +1363,7 @@ static void send_debug_config(struct control_server* server, struct control_clie
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);
}
/* ============================================================================
@@ -1240,6 +1382,88 @@ 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 TOPO_NODEQ* nq) {
if (!server || !nq || !nq->node) return;
uint8_t buf[8192];
int ser_len = topo_node_serialize(topo_groups_get_default(server->instance->topo_groups), nq, buf + 2, sizeof(buf) - 2);
if (ser_len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to serialize node info"); return; }
buf[0] = CTRL_BGP_CMD; buf[1] = CTRL_BGP_SUBCMD;
size_t data_size = 2 + (size_t)ser_len;
size_t np_size = sizeof(struct etcpmon_msg_header) + data_size;
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, (uint16_t)data_size, ETCPMON_RSP_NODE_INFO, 0);
memcpy(buffer + sizeof(*hdr), buf, data_size);
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 TOPO_GROUP* group = topo_groups_get_default(server->instance->topo_groups);
if (!group) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
/* Send local node first */
if (group->local_node) send_single_node_info(server, client, group->local_node);
/* Send all known remote nodes */
struct ll_entry* e = group->nodes ? group->nodes->head : NULL;
while (e) {
struct TOPO_NODEQ* nq = (struct TOPO_NODEQ*)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 TOPO_NODEQ* 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
* ============================================================================ */
+10 -3
View File
@@ -19,6 +19,8 @@ extern "C" {
/* Forward declarations */
struct UTUN_INSTANCE;
struct UASYNC;
struct TOPO_NODEQ;
struct ll_queue;
/* Forward declaration */
struct control_server;
@@ -36,9 +38,11 @@ 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 */
void* idle_timer; /* Client idle timeout */
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
uint8_t write_registered; /* 1 = write events registered in uasync */
};
/* Control server state */
@@ -113,6 +117,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 TOPO_NODEQ* node);
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
#ifdef __cplusplus
}
#endif
+9
View File
@@ -1,6 +1,11 @@
#ifndef CRC32_H
#define CRC32_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
@@ -16,4 +21,8 @@ uint32_t crc32_calc_ex(const uint8_t *data, size_t len, uint32_t initial_crc);
// Update CRC32 incrementally
uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len);
#ifdef __cplusplus
}
#endif
#endif // CRC32_H
+940
View File
@@ -0,0 +1,940 @@
// db_sync.c — Distributed content-addressed table with LMDB + peer sync via etcp_router
#include "db_sync.h"
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "topo_group.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/sha256.h"
#include "../lib/liblmdb/lmdb.h"
#include "../lib/platform_compat.h"
#include <string.h>
#define DEBUG_CATEGORY_DB_SYNC DEBUG_CATEGORY_DEBUG
// ---- LMDB key/value layout ----
// Key: 16 bytes = timestamp:8 BE || datahash:8 BE (sorted by timestamp first)
// Value: id:8 || chain_hash:32 || creator:8 || flags:1 || data_len:4 || data:variable
#define DB_VAL_OFF_ID 0
#define DB_VAL_OFF_CHAIN_HASH 8
#define DB_VAL_OFF_CREATOR 40
#define DB_VAL_OFF_FLAGS 48
#define DB_VAL_OFF_DATA_LEN 49
#define DB_VAL_OFF_DATA 53
#define DB_VAL_HDR_SIZE 53
// ---- Module state ----
struct DB_SYNC {
struct UTUN_INSTANCE* inst;
MDB_env* env;
MDB_dbi dbi_records;
MDB_dbi dbi_meta;
uint64_t next_id;
uint64_t last_connected_tb; // timebase when any peer was last connected (for TTL)
uint64_t last_timestamp_us; // гарантия монотонности timestamp
struct DB_SYNC_PEER {
uint64_t node_id;
uint8_t synced; // 0=not, 1=in_progress, 2=synced
}* peers;
int peer_count;
int peer_capacity;
void* ttl_timer;
void* peer_check_timer;
uint8_t enabled;
};
// ---- Forward declarations ----
static void db_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
static void db_sync_on_new_conn(struct ETCP_CONN* conn, void* arg);
static void db_sync_on_conn_up(struct ETCP_CONN* conn, void* arg);
static void db_sync_on_conn_down(struct ETCP_CONN* conn, void* arg);
static void db_sync_peer_check_cb(void* arg);
static void db_sync_ttl_cleanup_cb(void* arg);
static int db_sync_send(struct DB_SYNC* db, uint64_t dst_node_id, const uint8_t* payload, size_t len);
static void db_sync_initiate_sync(struct DB_SYNC* db, uint64_t peer_node_id);
// ---- LMDB helpers ----
static int db_lmdb_open(struct DB_SYNC* db, const char* path) {
int rc = mdb_env_create(&db->env);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_create failed: %s", mdb_strerror(rc)); return -1; }
rc = mdb_env_set_mapsize(db->env, DB_SYNC_DEFAULT_MAPSIZE);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_set_mapsize failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
rc = mdb_env_set_maxdbs(db->env, 3);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_set_maxdbs failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
rc = mdb_env_open(db->env, path, 0, 0644);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_open(%s) failed: %s", path, mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
MDB_txn* txn;
rc = mdb_txn_begin(db->env, NULL, 0, &txn);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: txn_begin failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
rc = mdb_dbi_open(txn, "records", MDB_CREATE, &db->dbi_records);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: dbi_open(records) failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(db->env); db->env=NULL; return -1; }
rc = mdb_dbi_open(txn, "meta", MDB_CREATE, &db->dbi_meta);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: dbi_open(meta) failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(db->env); db->env=NULL; return -1; }
rc = mdb_txn_commit(txn);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: txn_commit failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
// Read next_id from meta
MDB_txn* rt;
rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &rt);
if (rc == MDB_SUCCESS) {
MDB_val key, data; key.mv_size = 7; key.mv_data = (char*)"next_id";
rc = mdb_get(rt, db->dbi_meta, &key, &data);
if (rc == MDB_SUCCESS && data.mv_size >= 8) db->next_id = *(uint64_t*)data.mv_data;
else db->next_id = 1;
mdb_txn_abort(rt);
}
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: LMDB opened at %s, next_id=%llu", path, (unsigned long long)db->next_id);
return 0;
}
static void db_lmdb_close(struct DB_SYNC* db) {
if (!db->env) return;
mdb_env_close(db->env); db->env = NULL;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: LMDB closed");
}
// Build 16-byte key: timestamp:8 BE || datahash:8 BE
static void db_key_build(uint8_t key[16], uint64_t timestamp, uint64_t datahash) {
uint64_t ts_be = htobe64(timestamp), dh_be = htobe64(datahash);
memcpy(key, &ts_be, 8); memcpy(key + 8, &dh_be, 8);
}
// Get chain_hash at a given sorted position (0-based) via cursor. Returns 0 on success.
static int db_chain_hash_at(struct DB_SYNC* db, uint32_t pos, uint8_t chain_hash_out[32]) {
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: chain_hash_at txn_begin failed: %s", mdb_strerror(rc)); return -1; }
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc) { mdb_txn_abort(txn); return -1; }
MDB_val key, val;
rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
for (uint32_t i = 0; rc == 0 && i < pos; i++) rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) memcpy(chain_hash_out, (uint8_t*)val.mv_data + DB_VAL_OFF_CHAIN_HASH, 32);
else { mdb_cursor_close(cursor); mdb_txn_abort(txn); return -1; }
mdb_cursor_close(cursor); mdb_txn_abort(txn);
return 0;
}
// Get count of records
static uint32_t db_count(struct DB_SYNC* db) {
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rc) return 0;
MDB_stat stat; rc = mdb_stat(txn, db->dbi_records, &stat);
mdb_txn_abort(txn);
return rc == 0 ? (uint32_t)stat.ms_entries : 0;
}
// Get datahash at given sorted position
static int db_datahash_at(struct DB_SYNC* db, uint32_t pos, uint64_t* datahash) {
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rc) return -1;
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc) { mdb_txn_abort(txn); return -1; }
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
for (uint32_t i=0; rc==0 && i<pos; i++) rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
if (rc==0) {
*datahash = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
mdb_cursor_close(cursor); mdb_txn_abort(txn); return 0;
}
mdb_cursor_close(cursor); mdb_txn_abort(txn); return -1;
}
// ---- SHA256 helpers ----
static void db_sha256(const uint8_t* data, size_t len, uint8_t hash[32]) {
SC_SHA256_CTX ctx; sc_sha256_init(&ctx); sc_sha256_update(&ctx, data, len); sc_sha256_final(&ctx, hash);
}
// Compute datahash: first 64 bits of SHA256(json_data)
static uint64_t db_datahash(const uint8_t* data, size_t len) {
uint8_t hash[32]; db_sha256(data, len, hash);
uint64_t dh; memcpy(&dh, hash, 8); return dh;
}
// Compute chain_hash for a record
static void db_chain_hash_compute(const uint8_t prev_chain_hash[32], uint64_t id, uint64_t timestamp, uint64_t datahash, uint8_t out[32]) {
uint8_t buf[32+8+8+8]; // prev_chain_hash:32 + id:8 + timestamp:8 + datahash:8
memcpy(buf, prev_chain_hash, 32);
memcpy(buf+32, &id, 8); memcpy(buf+40, &timestamp, 8); memcpy(buf+48, &datahash, 8);
db_sha256(buf, 56, out);
}
// Get previous record's chain_hash (record before the given key in sorted order)
static int db_prev_chain_hash(struct DB_SYNC* db, const uint8_t key[16], uint8_t chain_hash[32]) {
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rc) return -1;
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc) { mdb_txn_abort(txn); return -1; }
MDB_val search_key, val; search_key.mv_size = 16; search_key.mv_data = (void*)key;
rc = mdb_cursor_get(cursor, &search_key, &val, MDB_SET_RANGE);
if (rc == 0) {
rc = mdb_cursor_get(cursor, &search_key, &val, MDB_PREV);
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) {
memcpy(chain_hash, (uint8_t*)val.mv_data + DB_VAL_OFF_CHAIN_HASH, 32);
mdb_cursor_close(cursor); mdb_txn_abort(txn); return 0;
}
}
// No previous record → use zero hash
memset(chain_hash, 0, 32);
mdb_cursor_close(cursor); mdb_txn_abort(txn);
return 0;
}
// ---- Insert record ----
static int db_record_insert(struct DB_SYNC* db, uint64_t id, uint64_t timestamp, uint64_t datahash, const char* json, size_t json_len) {
uint8_t key[16]; db_key_build(key, timestamp, datahash);
// Check duplicate
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert txn_begin failed: %s", mdb_strerror(rc)); return -1; }
MDB_val dup_key, dup_val; dup_key.mv_size = 16; dup_key.mv_data = key;
rc = mdb_get(txn, db->dbi_records, &dup_key, &dup_val);
if (rc == MDB_SUCCESS) { mdb_txn_abort(txn); return 1; } // already exists
// Compute chain_hash
uint8_t prev_ch[32]; db_prev_chain_hash(db, key, prev_ch);
uint8_t chain_hash[32]; db_chain_hash_compute(prev_ch, id, timestamp, datahash, chain_hash);
// Build value
size_t val_size = DB_VAL_HDR_SIZE + json_len;
uint8_t* val_buf = u_malloc(val_size);
if (!val_buf) { mdb_txn_abort(txn); return -1; }
uint64_t creator = db->inst->node_id;
memcpy(val_buf + DB_VAL_OFF_ID, &id, 8);
memcpy(val_buf + DB_VAL_OFF_CHAIN_HASH, chain_hash, 32);
memcpy(val_buf + DB_VAL_OFF_CREATOR, &creator, 8);
val_buf[DB_VAL_OFF_FLAGS] = 0;
*(uint32_t*)(val_buf + DB_VAL_OFF_DATA_LEN) = (uint32_t)json_len;
if (json_len > 0) memcpy(val_buf + DB_VAL_OFF_DATA, json, json_len);
MDB_val val; val.mv_size = val_size; val.mv_data = val_buf;
MDB_val k; k.mv_size = 16; k.mv_data = key;
rc = mdb_put(txn, db->dbi_records, &k, &val, 0);
u_free(val_buf);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_put failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); return -1; }
// Update chain_hash for subsequent records (out-of-order insert case)
// Check if there are records AFTER this key
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc == 0) {
MDB_val ckey, cval; ckey.mv_size = 16; ckey.mv_data = key;
rc = mdb_cursor_get(cursor, &ckey, &cval, MDB_SET);
if (rc == 0) {
uint8_t running_ch[32]; memcpy(running_ch, chain_hash, 32);
uint64_t running_id = id;
uint64_t running_ts = timestamp;
uint64_t running_dh = datahash;
// Move to next
while (mdb_cursor_get(cursor, &ckey, &cval, MDB_NEXT) == 0) {
if (cval.mv_size < DB_VAL_HDR_SIZE) continue;
uint64_t n_id = *(uint64_t*)((uint8_t*)cval.mv_data + DB_VAL_OFF_ID);
uint64_t n_ts = be64toh(*(uint64_t*)ckey.mv_data);
uint64_t n_dh = be64toh(*(uint64_t*)((uint8_t*)ckey.mv_data + 8));
uint8_t new_ch[32]; db_chain_hash_compute(running_ch, n_id, n_ts, n_dh, new_ch);
memcpy((uint8_t*)cval.mv_data + DB_VAL_OFF_CHAIN_HASH, new_ch, 32);
mdb_cursor_put(cursor, &ckey, &cval, MDB_CURRENT);
memcpy(running_ch, new_ch, 32);
}
}
mdb_cursor_close(cursor);
}
// Update next_id in meta
MDB_val mk, md; mk.mv_size = 7; mk.mv_data = (char*)"next_id"; md.mv_size = 8; md.mv_data = &id; id++;
mdb_put(txn, db->dbi_meta, &mk, &md, 0);
db->next_id = id;
rc = mdb_txn_commit(txn);
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert commit failed: %s", mdb_strerror(rc)); return -1; }
return 0;
}
// ---- Send ----
static int db_sync_send(struct DB_SYNC* db, uint64_t dst_node_id, const uint8_t* payload, size_t len) {
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "queue_entry_new failed"); return -1; }
uint8_t* buf = u_malloc(len + 1);
if (!buf) { queue_entry_free(entry); return -1; }
buf[0] = ETCP_RT_ID_DB_SYNC; memcpy(buf + 1, payload, len);
entry->dgram = buf; entry->len = len + 1;
int ret = etcp_route_send(db->inst, dst_node_id, entry, 0);
// etcp_route_send always takes ownership and frees entry in all code paths
return ret;
}
// ---- Peer management ----
static struct DB_SYNC_PEER* db_peer_find(struct DB_SYNC* db, uint64_t node_id) {
for (int i=0; i<db->peer_count; i++) if (db->peers[i].node_id == node_id) return &db->peers[i];
return NULL;
}
static struct DB_SYNC_PEER* db_peer_add(struct DB_SYNC* db, uint64_t node_id) {
struct DB_SYNC_PEER* p = db_peer_find(db, node_id);
if (p) return p;
if (db->peer_count >= db->peer_capacity) {
int nc = db->peer_capacity ? db->peer_capacity * 2 : 8;
struct DB_SYNC_PEER* np = u_realloc(db->peers, nc * sizeof(struct DB_SYNC_PEER));
if (!np) return NULL;
db->peers = np; db->peer_capacity = nc;
}
p = &db->peers[db->peer_count++];
p->node_id = node_id; p->synced = 0;
return p;
}
// ---- Sync protocol: INIT_SYNC (A→B) ----
static void db_handle_init_sync(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 36) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_SYNC too short %zu from %016llx", len, (unsigned long long)src_node_id); return; }
uint32_t peer_count = *(uint32_t*)payload;
const uint8_t* peer_last_ch = payload + 4;
uint32_t my_count = db_count(db);
uint32_t test_pos = (peer_count < my_count ? peer_count : my_count);
if (test_pos > 0) test_pos--;
// Send INIT_RESP
uint8_t resp[4096];
uint32_t resp_off = 0;
resp[resp_off++] = DB_MSG_INIT_RESP;
memcpy(resp+resp_off, &test_pos, 4); resp_off += 4;
uint8_t my_ch[32];
if (my_count == 0) {
memset(my_ch, 0, 32);
} else if (db_chain_hash_at(db, test_pos, my_ch) != 0) {
memset(my_ch, 0, 32);
}
memcpy(resp+resp_off, my_ch, 32); resp_off += 32;
// Always include sparse_count at position 37 (after test_pos + chain_hash)
// For complete case: sparse_count=0, no sparse entries follow
// For sparse case: sparse entries start at position 38
// Check if chain_hash matches
if (memcmp(my_ch, peer_last_ch, 32) == 0) {
resp[resp_off++] = 0; // sparse_count = 0
db_sync_send(db, src_node_id, resp, resp_off);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP complete to %016llx test_pos=%u", (unsigned long long)src_node_id, test_pos);
return;
}
// Skip sparse_count slot for now, write it at the end
uint32_t sparse_count_pos = resp_off;
resp_off++; // placeholder for sparse_count
// Build sparse hashes: chain_hash at positions test_pos-1, -2, -4, -8, -16, ...
int sparse_count = 0;
for (uint32_t k = 0; k < 16; k++) {
uint32_t step = (uint32_t)(1u << k); // 1, 2, 4, 8, ...
if (test_pos < step) break;
uint32_t pos = test_pos - step;
if (db_chain_hash_at(db, pos, my_ch) != 0) break;
if (resp_off + 36 > sizeof(resp)) break;
memcpy(resp+resp_off, &pos, 4); resp_off += 4;
memcpy(resp+resp_off, my_ch, 32); resp_off += 32;
sparse_count++;
}
resp[sparse_count_pos] = (uint8_t)sparse_count;
db_sync_send(db, src_node_id, resp, resp_off);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP to %016llx test_pos=%u sparse=%d", (unsigned long long)src_node_id, test_pos, sparse_count);
}
// ---- Sync protocol: INIT_RESP (B→A) ----
static void db_handle_init_resp(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 37) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP too short %zu from %016llx", len, (unsigned long long)src_node_id); return; }
uint32_t test_pos = *(uint32_t*)payload;
const uint8_t* peer_ch = payload + 4;
uint8_t sparse_count = payload[36];
uint8_t my_ch[32];
if (db_chain_hash_at(db, test_pos, my_ch) != 0) { memset(my_ch, 0, 32); }
// Check if complete
if (sparse_count == 0 && memcmp(my_ch, peer_ch, 32) == 0) {
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
if (p) p->synced = 2;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: sync complete with %016llx", (unsigned long long)src_node_id);
return;
}
// If peer has empty table (chain_hash is all zeros), send everything
{
uint8_t zero[32]; memset(zero, 0, 32);
if (memcmp(peer_ch, zero, 32) == 0 && sparse_count == 0) {
uint32_t my_count = db_count(db);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer %016llx has empty table, sending all %u records", (unsigned long long)src_node_id, my_count);
// Send all records in batches
uint32_t sent = 0;
while (sent < my_count) {
uint32_t batch = my_count - sent;
if (batch > DB_SEND_DATA_MAX) batch = DB_SEND_DATA_MAX;
uint8_t sdbuf[8192]; uint32_t off=0;
sdbuf[off++] = DB_MSG_SEND_DATA; memcpy(sdbuf+off, &sent, 4); off+=4;
uint16_t* rcp = (uint16_t*)(sdbuf+off); off+=2; uint16_t rc=0;
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rrc==0) { MDB_cursor* cursor; rrc=mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rrc==0) { MDB_val key,val; rrc=mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
uint32_t cur=0; while (rrc==0 && cur < sent) { rrc=mdb_cursor_get(cursor,&key,&val,MDB_NEXT); cur++; }
while (rrc==0 && rc < batch && off+val.mv_size+16 < 8000) {
uint64_t ts = be64toh(*(uint64_t*)key.mv_data); uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_ID); uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_DATA_LEN);
memcpy(sdbuf+off, &rid,8); off+=8; memcpy(sdbuf+off, &ts,8); off+=8; memcpy(sdbuf+off, &dh,8); off+=8;
memcpy(sdbuf+off, &dlen,4); off+=4; if (dlen>0) { memcpy(sdbuf+off, (uint8_t*)val.mv_data+DB_VAL_OFF_DATA, dlen); off+=dlen; }
rc++; cur++; rrc=mdb_cursor_get(cursor, &key, &val, MDB_NEXT); }
mdb_cursor_close(cursor); } mdb_txn_abort(txn); }
*rcp = rc; sent += rc;
db_sync_send(db, src_node_id, sdbuf, off);
if (rc == 0) break; // no more records
}
return;
}
}
if (memcmp(my_ch, peer_ch, 32) == 0) {
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
if (p) p->synced = 2;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: chain_hash match with %016llx at pos %u — synced", (unsigned long long)src_node_id, test_pos);
// Send our extra records if any (beyond test_pos)
uint32_t my_count = db_count(db);
if (my_count > test_pos + 1) {
// We have more records — send them
uint32_t from = test_pos + 1;
uint32_t remaining = my_count - from;
uint32_t batch = remaining < DB_SEND_DATA_MAX ? remaining : DB_SEND_DATA_MAX;
// Continue in SEND_DATA handler...
}
return;
}
// Find divergence range using sparse hashes
uint32_t div_start = 0, div_end = test_pos;
const uint8_t* sparse = payload + 37;
for (int i = 0; i < sparse_count && sparse + 36 <= payload + len; i++) {
uint32_t pos = *(uint32_t*)sparse;
const uint8_t* peer_sparse_ch = sparse + 4;
uint8_t my_sparse_ch[32];
if (db_chain_hash_at(db, pos, my_sparse_ch) == 0) {
if (memcmp(my_sparse_ch, peer_sparse_ch, 32) == 0) { if (pos + 1 > div_start) div_start = pos + 1; }
else { if (pos < div_end) div_end = pos; }
}
sparse += 36;
}
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: divergence with %016llx range [%u, %u]", (unsigned long long)src_node_id, div_start, div_end);
if (div_end - div_start <= 1) {
// Divergence is small — request data directly via REFINE round
uint8_t ref[512]; uint32_t ref_off = 0;
ref[ref_off++] = DB_MSG_REFINE;
memcpy(ref+ref_off, &div_start, 4); ref_off += 4;
memcpy(ref+ref_off, &div_end, 4); ref_off += 4;
uint8_t hcount = 0; ref[ref_off++] = hcount; // no hashes, just requesting data
db_sync_send(db, src_node_id, ref, ref_off);
} else {
// Send REFINE with our hashes
uint8_t ref[512]; uint32_t ref_off = 0;
ref[ref_off++] = DB_MSG_REFINE;
memcpy(ref+ref_off, &div_start, 4); ref_off += 4;
memcpy(ref+ref_off, &div_end, 4); ref_off += 4;
uint32_t range = div_end - div_start;
uint8_t count = range < DB_REFINE_HASHES ? (uint8_t)range : DB_REFINE_HASHES;
ref[ref_off++] = count;
for (uint8_t i = 0; i < count; i++) {
uint32_t pos = div_start + (range * i / count);
uint64_t dh;
if (db_datahash_at(db, pos, &dh) == 0) {
memcpy(ref+ref_off, &pos, 4); ref_off += 4;
memcpy(ref+ref_off, &dh, 8); ref_off += 8;
}
}
db_sync_send(db, src_node_id, ref, ref_off);
}
}
// ---- Sync protocol: REFINE ----
static void db_handle_refine(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 9) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: REFINE too short %zu", len); return; }
uint32_t from = *(uint32_t*)payload;
uint32_t to = *(uint32_t*)(payload+4);
uint8_t hcount = payload[8];
if (hcount == 0) {
// Peer requests data in range [from, to]
uint32_t my_count = db_count(db);
uint32_t send_from = from;
uint32_t send_count = (to - from + 1) < DB_SEND_DATA_MAX ? (to - from + 1) : DB_SEND_DATA_MAX;
if (send_from >= my_count) return; // nothing to send
// Build SEND_DATA
uint8_t* buf = u_malloc(8192);
if (!buf) return;
uint32_t off = 0; buf[off++] = DB_MSG_SEND_DATA;
memcpy(buf+off, &send_from, 4); off += 4;
uint16_t rec_count = 0;
uint16_t* cnt_ptr = (uint16_t*)(buf+off); off += 2;
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rc == 0) {
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc == 0) {
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
uint32_t cur = 0;
while (rc == 0 && cur < send_from) { rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT); cur++; }
while (rc == 0 && rec_count < send_count && off + val.mv_size + 16 < 8000) {
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data + 8));
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_ID);
uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_DATA_LEN);
const uint8_t* dptr = (uint8_t*)val.mv_data + DB_VAL_OFF_DATA;
memcpy(buf+off, &rid, 8); off += 8;
memcpy(buf+off, &ts, 8); off += 8;
memcpy(buf+off, &dh, 8); off += 8;
memcpy(buf+off, &dlen, 4); off += 4;
if (dlen > 0) { memcpy(buf+off, dptr, dlen); off += dlen; }
rec_count++;
rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
mdb_txn_abort(txn);
}
*cnt_ptr = rec_count;
db_sync_send(db, src_node_id, buf, off);
u_free(buf);
return;
}
// Peer sent hashes at positions — find mismatch
uint32_t first_mismatch = to + 1;
const uint8_t* hp = payload + 9;
for (uint8_t i = 0; i < hcount && hp + 12 <= payload + len; i++) {
uint32_t pos = *(uint32_t*)hp;
uint64_t peer_dh = *(uint64_t*)(hp + 4);
uint64_t my_dh;
if (db_datahash_at(db, pos, &my_dh) == 0 && my_dh == peer_dh) {
if (pos >= from) { if (pos + 1 < first_mismatch) first_mismatch = pos + 1; }
} else {
if (pos < first_mismatch) first_mismatch = pos;
}
hp += 12;
}
// Send SEND_DATA from first_mismatch
uint32_t my_count = db_count(db);
uint32_t send_count = 4;
if (first_mismatch > to || first_mismatch >= my_count) { send_count = 0; }
uint8_t sdbuf[8192]; uint32_t off = 0;
sdbuf[off++] = DB_MSG_SEND_DATA;
memcpy(sdbuf+off, &first_mismatch, 4); off += 4;
uint16_t rc_count = 0;
uint16_t* rcp = (uint16_t*)(sdbuf+off); off += 2;
if (send_count > 0) {
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rrc == 0) {
MDB_cursor* cursor; rrc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rrc == 0) {
MDB_val key, val; rrc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
uint32_t cur = 0;
while (rrc == 0 && cur < first_mismatch) { rrc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT); cur++; }
while (rrc == 0 && rc_count < send_count && off + val.mv_size + 16 < 8000) {
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data + 8));
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_ID);
uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_DATA_LEN);
const uint8_t* dptr = (uint8_t*)val.mv_data + DB_VAL_OFF_DATA;
memcpy(sdbuf+off, &rid, 8); off += 8;
memcpy(sdbuf+off, &ts, 8); off += 8;
memcpy(sdbuf+off, &dh, 8); off += 8;
memcpy(sdbuf+off, &dlen, 4); off += 4;
if (dlen > 0) { memcpy(sdbuf+off, dptr, dlen); off += dlen; }
rc_count++; rrc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
mdb_txn_abort(txn);
}
}
*rcp = rc_count;
db_sync_send(db, src_node_id, sdbuf, off);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: SEND_DATA to %016llx from=%u count=%u", (unsigned long long)src_node_id, first_mismatch, rc_count);
}
// ---- Sync protocol: SEND_DATA ----
static void db_handle_send_data(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 6) return;
uint32_t from = *(uint32_t*)payload;
uint16_t count = *(uint16_t*)(payload+4);
const uint8_t* ptr = payload + 6;
for (uint16_t i = 0; i < count; i++) {
if (ptr + 28 > payload + len) break;
uint64_t rid = *(uint64_t*)ptr; ptr += 8;
uint64_t ts = *(uint64_t*)ptr; ptr += 8;
uint64_t dh = *(uint64_t*)ptr; ptr += 8;
uint32_t dlen = *(uint32_t*)ptr; ptr += 4;
if (ptr + dlen > payload + len) break;
db_record_insert(db, rid, ts, dh, (const char*)ptr, dlen);
ptr += dlen;
}
// Send our extra records too (bidirectional)
uint32_t my_count = db_count(db);
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
uint32_t peer_known = from + count;
if (my_count > peer_known && p && p->synced == 1) {
// Send our records beyond what peer knows
uint32_t send_count = my_count - peer_known;
if (send_count > DB_SEND_DATA_MAX) send_count = DB_SEND_DATA_MAX;
uint8_t sdbuf[8192]; uint32_t off=0;
sdbuf[off++] = DB_MSG_SEND_DATA; memcpy(sdbuf+off, &peer_known, 4); off+=4;
uint16_t* rcp = (uint16_t*)(sdbuf+off); off+=2;
uint16_t rc=0;
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
if (rrc==0) {
MDB_cursor* cursor; rrc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rrc==0) {
MDB_val key,val; rrc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
uint32_t cur=0;
while (rrc==0 && cur < peer_known) { rrc=mdb_cursor_get(cursor,&key,&val,MDB_NEXT); cur++; }
while (rrc==0 && rc<send_count && off+val.mv_size+16 < 8000) {
uint64_t rts = be64toh(*(uint64_t*)key.mv_data);
uint64_t rdh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
uint64_t rrid = *(uint64_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_ID);
uint32_t rdlen = *(uint32_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_DATA_LEN);
const uint8_t* rdptr = (uint8_t*)val.mv_data+DB_VAL_OFF_DATA;
memcpy(sdbuf+off, &rrid,8); off+=8; memcpy(sdbuf+off, &rts,8); off+=8;
memcpy(sdbuf+off, &rdh,8); off+=8; memcpy(sdbuf+off, &rdlen,4); off+=4;
if (rdlen>0) { memcpy(sdbuf+off, rdptr, rdlen); off+=rdlen; }
rc++; rrc=mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
mdb_txn_abort(txn);
}
*rcp = rc;
db_sync_send(db, src_node_id, sdbuf, off);
}
// Verify sync complete
uint32_t new_count = db_count(db);
uint8_t my_last_ch[32];
if (new_count > 0 && db_chain_hash_at(db, new_count - 1, my_last_ch) == 0) {
uint8_t sd[37]; sd[0] = DB_MSG_SYNC_DONE;
memcpy(sd+1, &new_count, 4); memcpy(sd+5, my_last_ch, 32);
db_sync_send(db, src_node_id, sd, 37);
}
if (p) p->synced = 2;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: received %u records from %016llx", count, (unsigned long long)src_node_id);
}
// ---- Sync protocol: SYNC_DONE ----
static void db_handle_sync_done(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 36) return;
uint32_t peer_count = *(uint32_t*)payload;
const uint8_t* peer_ch = payload + 4;
uint32_t my_count = db_count(db);
uint8_t my_ch[32];
if (my_count > 0) db_chain_hash_at(db, my_count - 1, my_ch);
else memset(my_ch, 0, 32);
if (my_count != peer_count || memcmp(my_ch, peer_ch, 32) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "db_sync: SYNC_DONE mismatch with %016llx my=%u peer=%u — re-syncing",
(unsigned long long)src_node_id, my_count, peer_count);
db_sync_initiate_sync(db, src_node_id);
return;
}
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
if (p) p->synced = 2;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: sync confirmed with %016llx count=%u", (unsigned long long)src_node_id, my_count);
}
// ---- Sync protocol: ACK_PUSH ----
static void db_handle_ack_push(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
(void)src_node_id;
if (len < 16) return;
uint64_t dh = *(uint64_t*)payload;
uint64_t ts = *(uint64_t*)(payload + 8);
uint8_t key[16]; db_key_build(key, ts, dh);
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
if (rc) return;
MDB_val k, val; k.mv_size = 16; k.mv_data = key;
rc = mdb_get(txn, db->dbi_records, &k, &val);
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) {
uint64_t creator = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_CREATOR);
uint8_t flags = *(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS);
if (creator == db->inst->node_id && !(flags & DB_REC_FLAG_WAS_SENT)) {
flags |= DB_REC_FLAG_WAS_SENT;
*(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS) = flags;
mdb_put(txn, db->dbi_records, &k, &val, 0);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: ACK_PUSH mark sent dh=%016llx ts=%llu from %016llx", (unsigned long long)dh, (unsigned long long)ts, (unsigned long long)src_node_id);
}
}
mdb_txn_commit(txn);
}
// ---- Sync protocol: PUSH ----
static void db_handle_push(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
if (len < 28) return;
const uint8_t* ptr = payload;
uint64_t rid = *(uint64_t*)ptr; ptr += 8;
uint64_t ts = *(uint64_t*)ptr; ptr += 8;
uint64_t dh = *(uint64_t*)ptr; ptr += 8;
uint32_t dlen = *(uint32_t*)ptr; ptr += 4;
if (ptr + dlen > payload + len) return;
int ret = db_record_insert(db, rid, ts, dh, (const char*)ptr, dlen);
if (ret == 0) {
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: PUSH inserted from %016llx id=%llu dh=%016llx", (unsigned long long)src_node_id, (unsigned long long)rid, (unsigned long long)dh);
uint8_t ack[17]; ack[0] = DB_MSG_ACK_PUSH; memcpy(ack+1, &dh, 8); memcpy(ack+9, &ts, 8);
db_sync_send(db, src_node_id, ack, 17);
}
}
// ---- Main receive callback ----
static void db_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || entry->len < 2) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; }
struct DB_SYNC* db = conn ? conn->instance->db_sync : NULL;
if (!db || !db->enabled) { queue_dgram_free(entry); queue_entry_free(entry); return; }
uint64_t src_node_id = conn ? conn->peer_node_id : 0;
uint8_t type = entry->dgram[1];
const uint8_t* payload = entry->dgram + 2;
size_t plen = entry->len - 2;
switch (type) {
case DB_MSG_INIT_SYNC: db_handle_init_sync(db, src_node_id, payload, plen); break;
case DB_MSG_INIT_RESP: db_handle_init_resp(db, src_node_id, payload, plen); break;
case DB_MSG_REFINE: db_handle_refine(db, src_node_id, payload, plen); break;
case DB_MSG_SEND_DATA: db_handle_send_data(db, src_node_id, payload, plen); break;
case DB_MSG_PUSH: db_handle_push(db, src_node_id, payload, plen); break;
case DB_MSG_ACK_PUSH: db_handle_ack_push(db, src_node_id, payload, plen); break;
case DB_MSG_SYNC_DONE: db_handle_sync_done(db, src_node_id, payload, plen); break;
default: DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "db_sync: unknown msg type 0x%02x from %016llx", type, (unsigned long long)src_node_id); break;
}
queue_dgram_free(entry); queue_entry_free(entry);
}
// ---- Connection callbacks ----
static void db_sync_on_new_conn(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (!conn || !conn->instance || !conn->instance->db_sync) return;
etcp_conn_add_up_cbk(conn, db_sync_on_conn_up, NULL);
etcp_conn_add_down_cbk(conn, db_sync_on_conn_down, NULL);
}
static void db_sync_on_conn_up(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (!conn || !conn->instance || !conn->instance->db_sync) return;
struct DB_SYNC* db = conn->instance->db_sync;
uint64_t peer_id = conn->peer_node_id;
if (peer_id == 0 || peer_id == db->inst->node_id) return;
db->last_connected_tb = get_time_tb();
struct DB_SYNC_PEER* p = db_peer_add(db, peer_id);
if (p && p->synced == 0) {
p->synced = 1;
db_sync_initiate_sync(db, peer_id);
}
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer up %016llx", (unsigned long long)peer_id);
}
static void db_sync_on_conn_down(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (!conn || !conn->instance || !conn->instance->db_sync) return;
struct DB_SYNC* db = conn->instance->db_sync;
uint64_t peer_id = conn->peer_node_id;
if (peer_id == 0) return;
struct DB_SYNC_PEER* p = db_peer_find(db, peer_id);
if (p) p->synced = 0;
int any_up = 0;
for (int i = 0; i < db->peer_count; i++) { if (db->peers[i].synced >= 1) { any_up = 1; break; } }
if (!any_up) db->last_connected_tb = 0;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer down %016llx", (unsigned long long)peer_id);
}
// ---- Initiate sync ----
static void db_sync_initiate_sync(struct DB_SYNC* db, uint64_t peer_node_id) {
uint32_t my_count = db_count(db);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: initiate_sync to %016llx my_count=%u", (unsigned long long)peer_node_id, my_count);
uint8_t msg[37]; msg[0] = DB_MSG_INIT_SYNC;
memcpy(msg+1, &my_count, 4);
if (my_count > 0) {
if (db_chain_hash_at(db, my_count-1, msg+5) != 0) memset(msg+5, 0, 32);
} else {
memset(msg+5, 0, 32);
}
db_sync_send(db, peer_node_id, msg, 37);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_SYNC → %016llx my_count=%u", (unsigned long long)peer_node_id, my_count);
}
// ---- Periodic timers ----
static void db_sync_peer_check_cb(void* arg) {
struct DB_SYNC* db = (struct DB_SYNC*)arg;
if (!db || !db->enabled) return;
struct TOPO_GROUP* group = topo_groups_get_default(db->inst->topo_groups);
if (!group) { db->peer_check_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer"); return; }
// Check all known peers with active connections
struct ll_entry* e = group->senders_list->head;
while (e) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
if (item->conn && item->conn->peer_node_id != 0 && item->conn->links_up) {
uint64_t pid = item->conn->peer_node_id;
if (pid != db->inst->node_id) {
struct DB_SYNC_PEER* p = db_peer_add(db, pid);
if (p && p->synced == 0) { p->synced = 1; db_sync_initiate_sync(db, pid); }
}
}
e = e->next;
}
db->peer_check_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer");
}
static void db_sync_ttl_cleanup_cb(void* arg) {
struct DB_SYNC* db = (struct DB_SYNC*)arg;
if (!db || !db->enabled || !db->env) { if (db) db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl"); return; }
uint64_t my_id = db->inst->node_id;
uint64_t now_us = get_time_us();
uint64_t ttl_us = (uint64_t)(db->inst->config->global.db_sync_ttl) * 1000000uLL;
uint64_t cutoff = now_us - ttl_us;
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
if (rc != 0) { db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl"); return; }
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
if (rc == 0) {
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
while (rc == 0) {
if (val.mv_size >= DB_VAL_HDR_SIZE) {
uint64_t creator = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_CREATOR);
uint8_t flags = *(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS);
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
if (creator == my_id && !(flags & DB_REC_FLAG_WAS_SENT) && ts < cutoff) {
mdb_cursor_del(cursor, 0);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: TTL deleted unsent record ts=%llu dh=%016llx", (unsigned long long)ts, (unsigned long long)be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8)));
}
}
rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
mdb_txn_commit(txn);
db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl");
}
// ---- Public API ----
int db_sync_init(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "NULL instance"); return -1; }
struct DB_SYNC* db = u_calloc(1, sizeof(struct DB_SYNC));
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "u_calloc failed"); return -1; }
db->inst = inst; db->last_connected_tb = 0; db->last_timestamp_us = 0;
if (!inst->config->global.db_sync_enabled) {
db->enabled = 0; inst->db_sync = db;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: disabled by config");
return 0;
}
db->enabled = 1;
inst->db_sync = db;
// Open LMDB
const char* db_path = inst->config->global.db_path;
char sync_path[512];
if (db_path[0]) {
snprintf(sync_path, sizeof(sync_path), "%s/sync", db_path);
} else {
snprintf(sync_path, sizeof(sync_path), "/tmp/utun_db_sync");
}
if (db_lmdb_open(db, sync_path) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "LMDB open failed, sync disabled");
db->enabled = 0;
}
// Register in etcp_router
etcp_router_bind(inst, ETCP_RT_ID_DB_SYNC, db_sync_recv_cb);
// Subscribe to ETCP connection events
etcp_add_new_conn_cbk(inst, db_sync_on_new_conn, NULL);
// For existing connections: add up/down callbacks
{
struct ETCP_CONN* conn = inst->connections;
while (conn) {
etcp_conn_add_up_cbk(conn, db_sync_on_conn_up, NULL);
etcp_conn_add_down_cbk(conn, db_sync_on_conn_down, NULL);
conn = conn->next;
}
}
// Start periodic timers
db->peer_check_timer = uasync_set_timeout(inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer");
db->ttl_timer = uasync_set_timeout(inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl");
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: initialized (enabled=%d)", db->enabled);
return 0;
}
void db_sync_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->db_sync) return;
struct DB_SYNC* db = inst->db_sync;
inst->db_sync = NULL;
etcp_router_unbind(inst, ETCP_RT_ID_DB_SYNC);
if (db->peer_check_timer) { uasync_cancel_timeout(inst->ua, db->peer_check_timer); db->peer_check_timer = NULL; }
if (db->ttl_timer) { uasync_cancel_timeout(inst->ua, db->ttl_timer); db->ttl_timer = NULL; }
// Remove up/down callbacks from existing connections
{ struct ETCP_CONN* conn = inst->connections; while (conn) { etcp_conn_remove_up_cbk(conn, db_sync_on_conn_up, NULL); etcp_conn_remove_down_cbk(conn, db_sync_on_conn_down, NULL); conn = conn->next; } }
db_lmdb_close(db);
if (db->peers) u_free(db->peers);
u_free(db);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: destroyed");
}
int db_sync_insert_len(struct UTUN_INSTANCE* inst, const char* json_data, size_t len) {
if (!inst || !inst->db_sync || !json_data || len == 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "invalid args"); return -1; }
struct DB_SYNC* db = inst->db_sync;
if (!db->enabled) return -1;
// Compute datahash and timestamp
uint64_t datahash = db_datahash((const uint8_t*)json_data, len);
uint64_t now_us = get_time_us();
// Guarantee monotonicity
if (now_us <= db->last_timestamp_us) now_us = db->last_timestamp_us + 1;
db->last_timestamp_us = now_us;
uint64_t timestamp = now_us;
uint64_t id = db->next_id;
int ret = db_record_insert(db, id, timestamp, datahash, json_data, len);
if (ret != 0) return ret; // 1 = duplicate, -1 = error
// Broadcast PUSH to all synced peers
uint8_t pbuf[2048];
uint32_t off = 0; pbuf[off++] = DB_MSG_PUSH;
memcpy(pbuf+off, &id, 8); off += 8;
memcpy(pbuf+off, &timestamp, 8); off += 8;
memcpy(pbuf+off, &datahash, 8); off += 8;
memcpy(pbuf+off, &len, 4); off += 4;
if (len > 0 && off + len <= sizeof(pbuf)) { memcpy(pbuf+off, json_data, len); off += len; }
for (int i = 0; i < db->peer_count; i++) if (db->peers[i].synced >= 1 && db->peers[i].node_id != inst->node_id) db_sync_send(db, db->peers[i].node_id, pbuf, off);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert id=%llu dh=%016llx ts=%llu len=%zu", (unsigned long long)id, (unsigned long long)datahash, (unsigned long long)timestamp, len);
return 0;
}
int db_sync_insert(struct UTUN_INSTANCE* inst, const char* json_data) {
if (!json_data) return -1;
return db_sync_insert_len(inst, json_data, strlen(json_data));
}
uint32_t db_sync_count(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->db_sync) return 0;
return db_count(inst->db_sync);
}
+50
View File
@@ -0,0 +1,50 @@
// db_sync.h — Distributed content-addressed table with LMDB storage and peer sync via etcp_router
#ifndef DB_SYNC_H
#define DB_SYNC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
struct UTUN_INSTANCE;
// etcp_router service ID
#define ETCP_RT_ID_DB_SYNC 0x20
// Message types
#define DB_MSG_INIT_SYNC 0x01
#define DB_MSG_INIT_RESP 0x02
#define DB_MSG_REFINE 0x03
#define DB_MSG_SEND_DATA 0x04
#define DB_MSG_PUSH 0x05
#define DB_MSG_ACK_PUSH 0x06
#define DB_MSG_SYNC_DONE 0x07
// Defaults
#define DB_SYNC_DEFAULT_TTL 86400
#define DB_SYNC_DEFAULT_MAPSIZE (100UL * 1024 * 1024)
#define DB_SYNC_PEER_CHECK_INTERVAL 5
#define DB_SYNC_TTL_INTERVAL 3600
// Record flags
#define DB_REC_FLAG_WAS_SENT 0x01
// Sync protocol constants
#define DB_REFINE_HASHES 16
#define DB_SEND_DATA_MAX 32
int db_sync_init(struct UTUN_INSTANCE* inst);
void db_sync_destroy(struct UTUN_INSTANCE* inst);
int db_sync_insert(struct UTUN_INSTANCE* inst, const char* json_data);
int db_sync_insert_len(struct UTUN_INSTANCE* inst, const char* json_data, size_t len);
uint32_t db_sync_count(struct UTUN_INSTANCE* inst);
#ifdef __cplusplus
}
#endif
#endif // DB_SYNC_H
Binary file not shown.
+35
View File
@@ -577,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,
@@ -585,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) {
@@ -637,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;
}
+11
View File
@@ -21,6 +21,11 @@
#ifndef DUMMYNET_H
#define DUMMYNET_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
@@ -176,5 +181,11 @@ 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);
#ifdef __cplusplus
}
#endif
#endif // DUMMYNET_H
+9
View File
@@ -1,6 +1,11 @@
#ifndef EIM_NAT_H
#define EIM_NAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
@@ -51,4 +56,8 @@ int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
uint32_t internal_ip_host, uint16_t internal_port_net,
uint16_t external_port);
#ifdef __cplusplus
}
#endif
#endif
+287 -33
View File
@@ -3,8 +3,9 @@
#include "etcp.h"
#include "etcp_debug.h"
#include "etcp_loadbalancer.h"
#include "etcp_router.h"
#include "routing.h"
#include "route_bgp.h"
#include "topo_group.h"
#include "route_ping.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
@@ -15,6 +16,7 @@
#include <sys/time.h>
#include <math.h> // For bandwidth calcs
#include <limits.h> // For UINT16_MAX
#include <time.h> // For strftime in metrics snapshot
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
@@ -70,7 +72,7 @@ static int inflight_seq_cmp(const void* a, const void* b) {
static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
uint8_t* dgram, uint16_t dgram_len,
uint8_t** asm_buf, uint16_t* asm_len, uint16_t* asm_cap,
uint8_t** asm_buf, uint16_t* asm_len, uint32_t* asm_cap,
uint32_t* returned) {
uint32_t need = (uint32_t)*asm_len + dgram_len;
if (need >= ASM_BUF_MAX_SIZE) {
@@ -78,9 +80,10 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
return;
}
if (need > *asm_cap) {
uint16_t new_cap = *asm_cap ? *asm_cap : 256;
uint32_t new_cap = *asm_cap ? *asm_cap : 256;
while (new_cap < need) new_cap *= 2;
uint8_t* new_buf = u_realloc(*asm_buf, new_cap);
if (new_cap > 0xFFFF) new_cap = 0xFFFF;
uint8_t* new_buf = u_realloc(*asm_buf, (uint16_t)new_cap);
if (!new_buf) return;
*asm_buf = new_buf;
*asm_cap = new_cap;
@@ -93,7 +96,7 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
if (pkt_len == 0 || pkt_len > 16384 || *asm_len < 2 + pkt_len) break;
const uint8_t* pkt_data = *asm_buf + 2;
if (pkt_data[0] != ETCP_ID_ROUTE_ENTRY) {
if (pkt_data[0] != ETCP_ID_TOPO_ENTRY) {
struct ll_entry* e = ll_alloc_lldgram(pkt_len);
if (e) {
memcpy(e->dgram, pkt_data, pkt_len);
@@ -138,7 +141,7 @@ static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) {
uint8_t* asm_buf = NULL;
uint16_t asm_len = 0;
uint16_t asm_cap = 0;
uint32_t asm_cap = 0;
uint32_t returned_packets = 0;
for (int i = 0; i < inflight_count; i++) {
@@ -193,7 +196,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
struct ETCP_CONN* etcp = u_calloc(1, sizeof(struct ETCP_CONN));
if (!etcp) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: creating connection failed for instance %p", instance);
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "creating connection failed for instance %p", instance);
return NULL;
}
@@ -201,6 +204,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
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->transit_queues = queue_new(instance->ua, TRANSIT_QUEUE_HASH, 0, 16, "transit_q_reg"); // Hash for transit queues
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
@@ -216,9 +220,10 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
// Initialize log_name with local node_id (peer will be updated later when known)
snprintf(etcp->log_name, sizeof(etcp->log_name), "%04X->???? [%s]", (uint16_t)instance->node_id, etcp->name);
if (!etcp->input_queue || !etcp->output_queue || !etcp->input_send_q || !etcp->recv_q || !etcp->ack_q ||
if (!etcp->input_queue || !etcp->output_queue || !etcp->transit_queues ||
!etcp->input_send_q || !etcp->recv_q || !etcp->ack_q ||
!etcp->input_wait_ack || !etcp->inflight_pool || !etcp->io_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: error - closing - input:%p output:%p send:%p wait:%x pool:%p",
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "error - closing - input:%p output:%p send:%p wait:%x pool:%p",
etcp->input_queue, etcp->output_queue, etcp->input_send_q, etcp->input_wait_ack, etcp->inflight_pool);
etcp_connection_close(etcp);
return NULL;
@@ -239,6 +244,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp_connection_close(etcp);
return NULL;
}
etcp->send_input_q = etcp->normalizer->input;
// Set input queue callback
queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
@@ -252,8 +258,9 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->log_name, etcp, etcp->mtu, etcp->next_tx_id);
// Вызываем callback для нового соединения если установлен
if (instance && instance->etcp_new_conn_cbk) {
instance->etcp_new_conn_cbk(etcp, instance->etcp_new_conn_arg);
if (instance) {
struct etcp_cbk_entry* cbe = instance->new_conn_cbks;
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
}
return etcp;
@@ -261,14 +268,15 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
static void etcp_on_up(struct ETCP_CONN* etcp) {
// if (!etcp->initialized) return;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] initialized=%d", etcp->log_name, etcp->initialized);
if (etcp->up_cbk) etcp->up_cbk(etcp, etcp->up_arg);
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] UP links_up=%d initialized=%d", etcp->log_name, etcp->links_up, etcp->initialized);
struct etcp_cbk_entry* cbe = etcp->up_cbks;
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
}
static void etcp_on_down(struct ETCP_CONN* etcp) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s]", etcp->log_name);
if (etcp->down_cbk) etcp->down_cbk(etcp, etcp->down_arg);
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] DOWN links_up=%d", etcp->log_name, etcp->links_up);
struct etcp_cbk_entry* cbe = etcp->down_cbks;
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
}
@@ -291,12 +299,16 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
uasync_cancel_timeout(etcp->instance->ua, etcp->ack_resp_timer);
etcp->ack_resp_timer = NULL;
}
etcp_metrics_stop_timer(etcp);
routing_del_conn(etcp);
// NOTE: route_bgp_remove_conn is already called via down_cbk in etcp_on_down() above
// NOTE: topo_bgp_remove_conn is already called via down_cbk in etcp_on_down() above
// DO NOT call it again here to avoid double-processing and ref_count corruption
// Очистить транзитные очереди (до pn_deinit/stcp_link_close — waiter'ы на normalizer->input/tx_queue)
etcp_router_transit_queues_destroy(etcp);
// Deinitialize packet normalizer (this will call routing_del_conn)
if (etcp->normalizer) {
pn_deinit((struct PKTNORM*)etcp->normalizer);
@@ -324,6 +336,11 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
drain_and_free_queue(&etcp->recv_q);
drain_and_free_queue(&etcp->ack_q);
// Free callback chains
{ struct etcp_cbk_entry* cbe = etcp->ready_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->ready_cbks = NULL; }
{ struct etcp_cbk_entry* cbe = etcp->up_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->up_cbks = NULL; }
{ struct etcp_cbk_entry* cbe = etcp->down_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->down_cbks = NULL; }
// Free memory pools after all elements are returned
if (etcp->inflight_pool) {
memory_pool_destroy(etcp->inflight_pool);
@@ -337,6 +354,14 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
u_free(etcp->name);
{ struct UTUN_INSTANCE* inst = etcp->instance;
if (inst && inst->connections) {
struct ETCP_CONN** pp = &inst->connections;
while (*pp && *pp != etcp) pp = &(*pp)->next;
if (*pp) { *pp = etcp->next; if (inst->connections_count) inst->connections_count--; }
}
}
// Clear next pointer to prevent dangling references
etcp->next = NULL;
@@ -374,7 +399,10 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
etcp->last_rr_link = NULL;
// Return unconfirmed inflight data to normalizer input (до нормалайзера)
if (etcp->normalizer) etcp_return_inflight_to_normalizer(etcp);
if (etcp->normalizer) {
etcp->normalizer->input->callback_suspended = 1;
etcp_return_inflight_to_normalizer(etcp);
}
// Clear queues (keep queue structures)
clear_queue(etcp->input_queue);
@@ -453,6 +481,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");
}
@@ -473,8 +506,8 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме
etcp->initialized = 0;// еще раз придёт conn_ready_callback
// Отменяем висящие NAT-ping'и для этого соединения
if (etcp->instance && etcp->instance->bgp) {
route_ping_cancel_for_conn(etcp->instance->bgp, etcp);
if (etcp->instance && etcp->instance->topo_groups) {
route_ping_cancel_for_conn(topo_groups_get_default(etcp->instance->topo_groups), etcp);
}
// Сбрасываем NAT-статус у всех линков этого соединения
@@ -502,8 +535,10 @@ void etcp_conn_ready(struct ETCP_CONN* conn) {
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Connection ready", conn->log_name);
etcp_metrics_start_timer(conn);
// Вызываем callback если установлен
if (conn->ready_cbk) conn->ready_cbk(conn, conn->ready_arg);
{ struct etcp_cbk_entry* cbe = conn->ready_cbks; while (cbe) { cbe->fn(conn, cbe->arg); cbe = cbe->next; } }
if (conn->links_up) etcp_on_up(conn);
}
@@ -717,7 +752,7 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
}
etcp->unacked_bytes += len;
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_queue_cb: successfully moved from input_queue to input_send_q");
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "successfully moved from input_queue to input_send_q");
// etcp_conn_process_send_queue(etcp);// сразу обработаем этот пакет
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] nextloop, input_queue size=%d ", etcp->log_name, q->count);
@@ -757,6 +792,7 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
// Update stats
etcp->retransmissions_count++;
etcp_metrics_add_loss(etcp, 1);
}
else {// не надо до конца сканировать - они уже сортированы по таймстемпу т.к. очередь fifo, а timestamp = время добавления в очередь = время отправки
// shedule timer
@@ -864,7 +900,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);
@@ -934,8 +970,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
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 (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
loadbalancer_link_ready(inf_pkt->last_link);
}
// Always add to the CURRENT link (first send or retransmission)
@@ -955,6 +989,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
inf_pkt->state=INFLIGHT_STATE_WAIT_ACK;
queue_data_put_with_index(etcp->input_wait_ack, &inf_pkt->ll);// move dgram to wait_ack queue
etcp_metrics_add_sent(etcp, inf_pkt->ll.len);
}
size_t ack_q_size = queue_entry_count(etcp->ack_q);
@@ -1095,7 +1130,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
}
}
// фрейм data (0) обязательно в конец
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX DATA: seq=%u len=%u retry=%d; Qlen: in=%d snd=%d wait=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, inf_pkt->send_count, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TX DATA: seq=%u len=%u retry=%d; Qlen: in=%d snd=%d wait=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, inf_pkt->send_count, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
dgram->data[ptr++]=0;// payload
dgram->data[ptr++]=inf_pkt->seq;
@@ -1175,8 +1210,9 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
// Add to output_queue using the same ETCP_FRAGMENT structure
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
next_expected_id, etcp->output_queue->count);
uint16_t pkt_len = rx_pkt->ll.len;
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
delivered_bytes += rx_pkt->ll.len;
delivered_bytes += pkt_len;
delivered_count++;
} else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
@@ -1220,7 +1256,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
if (!acked_pkt) {
// Packet might be already acknowledged or not found
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_ack_recv: packet seq=%u not found in wait_ack queue", seq);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "packet seq=%u not found in wait_ack queue", seq);
return;
}
@@ -1299,7 +1335,7 @@ 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);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
if (!pkt->data_len) {
@@ -1352,6 +1388,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t ret_ts=data[1] | (data[2]<<8);// cur_ts=ret_ts = RTT
uint16_t new_rtt=cur_ts-ret_ts;
pkt->link->rtt_last=new_rtt;
etcp_metrics_add_rtt(etcp, new_rtt);
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
@@ -1397,7 +1434,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
// формируем ACK
uint32_t seq=data[1] | (data[2]<<8) | (data[3]<<16) | (data[4]<<24);
if (etcp->got_initial_pkt == 0) {
if (seq==1) etcp->got_initial_pkt = 1;
if (seq==1) { etcp->got_initial_pkt = 1; DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Initial packet seq=1 received", etcp->log_name); }
else {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] Waiting for initial packet but recv seq=%d, ignoring", etcp->log_name, seq);
len=0;
@@ -1471,6 +1508,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
memcpy(payload_data, data + 5, pkt_len);
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);
etcp_metrics_add_rcvd(etcp, pkt_len);
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
} else {
etcp->rx_dup_count++;
@@ -1496,7 +1534,10 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
link->burst_recv_received = 0;
}
if (link->burst_recv_valid) {
if (seq != link->burst_recv_next_seq) link->burst_recv_valid = 0;
if (seq != link->burst_recv_next_seq) {
if (seq > link->burst_recv_next_seq) etcp_metrics_add_loss(etcp, seq - link->burst_recv_next_seq);
link->burst_recv_valid = 0;
}
if (seq < 16) {
link->burst_recv_times[seq] = get_time_us();
link->burst_recv_next_seq = seq + 1;
@@ -1563,8 +1604,46 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
break;
}
case ETCP_SECTION_METRICS: {
if (len < 2 + SC_SIGN_SIZE) { len = 0; break; }
uint16_t csv_len = data[1] | (data[2] << 8);
if ((uint32_t)(2 + SC_SIGN_SIZE + csv_len) > len) { len = 0; break; }
uint8_t* sig = data + 2;
uint8_t* csv = data + 2 + SC_SIGN_SIZE;
uint8_t* peer_pubkey = pkt->link->remote_ed25519_pubkey;
struct sc_stream_sign_state verify_state;
int sig_ok = (sc_stream_sign_verify_init(&verify_state, peer_pubkey) == SC_OK
&& sc_stream_sign_update(&verify_state, csv, csv_len) == SC_OK
&& sc_stream_sign_verify(&verify_state, sig, SC_SIGN_SIZE) == SC_OK);
if (!sig_ok && verify_state.initialized) /* update failed, then verify didn't run */
sc_stream_sign_cleanup(&verify_state);
if (sig_ok) {
const char* stats_dir = etcp->instance->stats_dir;
if (stats_dir[0] && etcp->peer_node_id != 0) {
char path[768];
snprintf(path, sizeof(path), "%s/from_%016llx.dat", stats_dir, (unsigned long long)etcp->peer_node_id);
FILE* f = fopen(path, "a");
if (f) {
fprintf(f, "%.*s,", csv_len, csv);
for (int i = 0; i < SC_SIGN_SIZE; i++) fprintf(f, "%02x", sig[i]);
fprintf(f, "\n");
fclose(f);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Received metrics from peer (%u bytes)", etcp->log_name, csv_len);
if (etcp->instance->api_bindings.on_metrics_rcvd)
etcp->instance->api_bindings.on_metrics_rcvd(etcp->instance->api_bindings.metrics_user_ptr, etcp, csv, csv_len, sig);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] Cannot open from_metrics file: %s", etcp->log_name, path);
}
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "[%s] Metrics signature verification FAILED", etcp->log_name);
}
data += 2 + SC_SIGN_SIZE + csv_len; len -= 2 + SC_SIGN_SIZE + csv_len;
break;
}
default:
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: unknown section type=0x%02x", type);
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "unknown section type=0x%02x", type);
len=0;
break;
}
@@ -1572,7 +1651,7 @@ 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);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {}
}
@@ -1597,3 +1676,178 @@ void etcp_update_mtu(struct ETCP_CONN* etcp) {
}
}
// ====================================================================== Метрики: гистограммы RTT/потерь + тотальные счётчики
static const uint16_t etcp_metrics_rtt_bounds[] = { 10, 20, 30, 50, 80, 130, 210, 340, 550, 900, 1500 };
#define ETCP_METRICS_RTT_BOUNDS_COUNT (sizeof(etcp_metrics_rtt_bounds) / sizeof(etcp_metrics_rtt_bounds[0]))
void etcp_metrics_init(struct etcp_metrics* m) {
if (!m) return;
memset(m, 0, sizeof(*m));
}
void etcp_metrics_add_rtt(struct ETCP_CONN* etcp, uint16_t rtt_tb) {
if (!etcp) return;
struct etcp_metrics* m = &etcp->metrics;
int i;
for (i = 0; i < (int)ETCP_METRICS_RTT_BOUNDS_COUNT; i++) {
if (rtt_tb < etcp_metrics_rtt_bounds[i]) break;
}
if (i >= ETCP_METRICS_RTT_BUCKETS) i = ETCP_METRICS_RTT_BUCKETS - 1;
m->rtt_hist[i]++;
m->work_samples++;
}
void etcp_metrics_add_sent(struct ETCP_CONN* etcp, uint32_t len) {
if (!etcp) return;
struct etcp_metrics* m = &etcp->metrics;
m->work_sent++;
m->work_bytes_sent += len;
m->total_sent++;
m->total_bytes_sent += len;
}
void etcp_metrics_add_rcvd(struct ETCP_CONN* etcp, uint32_t len) {
if (!etcp) return;
struct etcp_metrics* m = &etcp->metrics;
m->work_rcvd++;
m->work_bytes_rcvd += len;
m->total_rcvd++;
m->total_bytes_rcvd += len;
}
void etcp_metrics_add_loss(struct ETCP_CONN* etcp, uint32_t count) {
if (!etcp) return;
struct etcp_metrics* m = &etcp->metrics;
m->work_lost += count;
m->total_lost += count;
}
static void etcp_metrics_do_snapshot(struct ETCP_CONN* etcp) {
if (!etcp || !etcp->instance) return;
struct etcp_metrics* m = &etcp->metrics;
// Копируем working → final
memcpy(m->rtt_hist_final, m->rtt_hist, sizeof(m->rtt_hist_final));
memcpy(m->loss_hist_final, m->loss_hist, sizeof(m->loss_hist_final));
m->samples_final = m->work_samples;
m->sent_final = m->work_sent;
m->rcvd_final = m->work_rcvd;
m->bytes_sent_final = m->work_bytes_sent;
m->bytes_rcvd_final = m->work_bytes_rcvd;
m->lost_final = m->work_lost;
// Рассчитываем loss-гистограмму
uint32_t total = m->work_sent + m->work_lost;
if (total > 0) {
uint32_t loss_pct = (m->work_lost * 100 + total / 2) / total;
int bucket = (int)loss_pct;
if (bucket >= ETCP_METRICS_LOSS_BUCKETS) bucket = ETCP_METRICS_LOSS_BUCKETS - 1;
m->loss_hist[bucket]++;
}
// Обнуляем рабочую копию
memset(m->rtt_hist, 0, sizeof(m->rtt_hist));
memset(m->loss_hist, 0, sizeof(m->loss_hist));
m->work_samples = 0;
m->work_sent = 0; m->work_rcvd = 0;
m->work_bytes_sent = 0; m->work_bytes_rcvd = 0;
m->work_lost = 0;
m->last_snapshot_tb = get_time_tb();
// Строим CSV строку
char csv_buf[4096];
int pos = 0;
time_t now = time(NULL);
struct tm* tm = localtime(&now);
char date_str[16], time_str[16];
if (tm) { strftime(date_str, sizeof(date_str), "%Y-%m-%d", tm); strftime(time_str, sizeof(time_str), "%H:%M:%S", tm); }
else { strcpy(date_str, "----"); strcpy(time_str, "----"); }
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, "%s,%s", date_str, time_str);
for (int i = 0; i < ETCP_METRICS_RTT_BUCKETS; i++) pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u", m->rtt_hist_final[i]);
for (int i = 0; i < ETCP_METRICS_LOSS_BUCKETS; i++) pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u", m->loss_hist_final[i]);
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u,%u,%u,%u,%u,%u", m->samples_final, m->sent_final, m->rcvd_final, m->bytes_sent_final, m->bytes_rcvd_final, m->lost_final);
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%llu,%llu,%llu,%llu,%llu", (unsigned long long)m->total_sent, (unsigned long long)m->total_rcvd, (unsigned long long)m->total_bytes_sent, (unsigned long long)m->total_bytes_rcvd, (unsigned long long)m->total_lost);
uint32_t csv_len = (uint32_t)pos;
// Запись локального файла
const char* stats_dir = etcp->instance->stats_dir;
if (stats_dir[0] && etcp->peer_node_id != 0) {
char path[768];
snprintf(path, sizeof(path), "%s/%016llx.dat", stats_dir, (unsigned long long)etcp->peer_node_id);
FILE* f = fopen(path, "a");
if (f) { fprintf(f, "%s\n", csv_buf); fclose(f); }
else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] Cannot open metrics file: %s", etcp->log_name, path);
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Metrics snapshot: samples=%u sent=%u rcvd=%u lost=%u", etcp->log_name, m->samples_final, m->sent_final, m->rcvd_final, m->lost_final);
// Подпись Ed25519 и отправка пиру
if (etcp->crypto_ctx.initialized && etcp->initialized) {
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&etcp->crypto_ctx, &sign_state) == SC_OK) {
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
if (sc_stream_sign_update(&sign_state, (uint8_t*)csv_buf, csv_len) == SC_OK
&& sc_stream_sign_final(&sign_state, sig, &sig_len) == SC_OK) {
// Построить и отправить дграмму с секцией METRICS
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
if (link) {
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool);
if (dgram) {
dgram->link = link;
dgram->noencrypt_len = 0;
dgram->timestamp = get_current_timestamp();
int ptr = 0;
// Минимальная ACK-секция
dgram->data[ptr++] = 1; dgram->data[ptr++] = 0;
dgram->data[ptr++] = etcp->last_delivered_id; dgram->data[ptr++] = etcp->last_delivered_id>>8;
dgram->data[ptr++] = etcp->last_delivered_id>>16; dgram->data[ptr++] = etcp->last_delivered_id>>24;
dgram->data[ptr++] = etcp->rx_dup_count; dgram->data[ptr++] = etcp->rx_dup_count>>8;
// METRICS секция
dgram->data[ptr++] = ETCP_SECTION_METRICS;
uint16_t csv16 = (uint16_t)csv_len;
dgram->data[ptr++] = csv16 & 0xFF; dgram->data[ptr++] = (csv16 >> 8) & 0xFF;
memcpy(dgram->data + ptr, sig, SC_SIGN_SIZE); ptr += SC_SIGN_SIZE;
memcpy(dgram->data + ptr, csv_buf, csv_len); ptr += csv_len;
dgram->data_len = ptr;
etcp_encrypt_send(dgram);
memory_pool_free(etcp->instance->pkt_pool, dgram);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics sent to peer (%u bytes)", etcp->log_name, csv_len);
}
}
} else {
sc_stream_sign_cleanup(&sign_state);
}
}
}
}
static void metrics_snapshot_timer_cb(void* arg) {
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
if (!etcp) return;
etcp->metrics.timer = NULL;
if (etcp->metrics.work_sent + etcp->metrics.work_rcvd >= ETCP_METRICS_MIN_PACKETS)
etcp_metrics_do_snapshot(etcp);
// Перезапускаем таймер
etcp->metrics.timer = uasync_set_timeout(etcp->instance->ua, ETCP_METRICS_INTERVAL_TB, etcp, metrics_snapshot_timer_cb, "etcp_metrics");
}
void etcp_metrics_start_timer(struct ETCP_CONN* etcp) {
if (!etcp || etcp->metrics.timer) return;
etcp_metrics_init(&etcp->metrics);
etcp->metrics.timer = uasync_set_timeout(etcp->instance->ua, ETCP_METRICS_INTERVAL_TB, etcp, metrics_snapshot_timer_cb, "etcp_metrics");
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics timer started (interval=%ums)", etcp->log_name, ETCP_METRICS_INTERVAL_TB / 10);
}
void etcp_metrics_stop_timer(struct ETCP_CONN* etcp) {
if (!etcp) return;
if (etcp->metrics.timer && etcp->instance) {
uasync_cancel_timeout(etcp->instance->ua, etcp->metrics.timer);
etcp->metrics.timer = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics timer stopped", etcp->log_name);
}
}
+63 -9
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;
@@ -20,6 +21,11 @@ extern "C" {
struct UTUN_INSTANCE;
struct ETCP_CONN;
typedef void (*etcp_on_conn_ready)(struct ETCP_CONN* conn, void* arg);
struct etcp_cbk_entry {
etcp_on_conn_ready fn;
void* arg;
struct etcp_cbk_entry* next;
};
struct UASYNC;
uint16_t get_current_timestamp(void);
@@ -31,6 +37,7 @@ uint16_t get_current_timestamp(void);
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
#define ETCP_SECTION_METRICS 0x0A // Metrics CSV exchange (ed25519 signed)
// Burst measurement constants
#define BURST_PACKET_COUNT 12 // число пакетов в burst
@@ -54,6 +61,41 @@ uint16_t get_current_timestamp(void);
// MEAS_RESP valid
#define MEAS_RESP_VALID 1
// Метрики: гистограммы RTT и потерь с 10-мин снимками
#define ETCP_METRICS_RTT_BUCKETS 12 // 0-1,1-2,2-3,3-5,5-8,8-13,13-21,21-34,34-55,55-90,90-150,150+ ms
#define ETCP_METRICS_LOSS_BUCKETS 11 // 0%,1%,...,10%+
#define ETCP_METRICS_INTERVAL_TB 6000000 // 10 мин в 0.1ms
#define ETCP_METRICS_MIN_PACKETS 200 // мин пакетов для обновления гистограмм
struct etcp_metrics {
uint32_t rtt_hist[ETCP_METRICS_RTT_BUCKETS]; // рабочая RTT-гистограмма
uint32_t loss_hist[ETCP_METRICS_LOSS_BUCKETS]; // рабочая loss-гистограмма
uint32_t work_samples; // RTT замеров в окне
uint32_t work_sent; // пакетов отправлено в окне
uint32_t work_rcvd; // пакетов принято в окне
uint32_t work_bytes_sent; // байт отправлено в окне
uint32_t work_bytes_rcvd; // байт принято в окне
uint32_t work_lost; // потеряно в окне
uint32_t rtt_hist_final[ETCP_METRICS_RTT_BUCKETS]; // финальная RTT-гистограмма
uint32_t loss_hist_final[ETCP_METRICS_LOSS_BUCKETS]; // финальная loss-гистограмма
uint32_t samples_final;
uint32_t sent_final;
uint32_t rcvd_final;
uint32_t bytes_sent_final;
uint32_t bytes_rcvd_final;
uint32_t lost_final;
uint64_t total_sent;
uint64_t total_rcvd;
uint64_t total_bytes_sent;
uint64_t total_bytes_rcvd;
uint64_t total_lost;
void* timer;
uint64_t last_snapshot_tb;
};
#define INFLIGHT_STATE_WAIT_ACK 0
#define INFLIGHT_STATE_WAIT_SEND 1
#define INFLIGHT_INITIAL_HASH_SIZE 1024
@@ -104,6 +146,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;
@@ -111,6 +154,7 @@ struct ETCP_CONN {
// Peer info
uint64_t peer_node_id; // Peer node ID
uint8_t peer_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT)
// ============ Processing incoming data to be sent by ETCP
struct ll_queue* input_queue; // Incoming packets to send (rx_pool -> ETCP_FRAGMENT)
@@ -128,10 +172,12 @@ struct ETCP_CONN {
void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные
struct ll_queue* output_queue; // Assembled outgoing packets (storage: ETCP_FRAGMENT / rx_pool)
struct ll_queue* transit_queues; // hash по (src_node_id:8, dst_node_id:8) — транзитные очереди
struct ll_queue* send_input_q; // единая входная очередь отправки (normalizer->input или tx_queue)
// 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
@@ -173,20 +219,18 @@ struct ETCP_CONN {
// uint32_t total_packets_sent; // Total packets sent counter - Not used
// Flags
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
// Callback for ready notification
etcp_on_conn_ready ready_cbk; // callback при готовности соединения
void* ready_arg; // аргумент для ready_cbk
etcp_on_conn_ready up_cbk; // callback при готовности соединения
void* up_arg; // аргумент для ready_cbk
etcp_on_conn_ready down_cbk; // callback при готовности соединения
void* down_arg; // аргумент для ready_cbk
// Callback chains for ready/up/down notifications
struct etcp_cbk_entry* ready_cbks; // цепочка callback'ов при готовности соединения
struct etcp_cbk_entry* up_cbks; // цепочка callback'ов при поднятии канала
struct etcp_cbk_entry* down_cbks; // цепочка callback'ов при падении канала
void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен)
uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
@@ -196,6 +240,8 @@ struct ETCP_CONN {
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
uint32_t debug[8]; // 8 значений для дебага (live watch)
struct etcp_metrics metrics; // гистограммы RTT/потерь, тотальные счётчики
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
char log_name[256];
char* name; // Connection name from config (e.g., "client_test1"), or NULL/empty
@@ -243,6 +289,14 @@ void etcp_on_link_down(struct ETCP_CONN* etcp);
void etcp_update_mtu(struct ETCP_CONN* etcp);
void etcp_metrics_init(struct etcp_metrics* m);
void etcp_metrics_add_rtt(struct ETCP_CONN* etcp, uint16_t rtt_tb);
void etcp_metrics_add_sent(struct ETCP_CONN* etcp, uint32_t len);
void etcp_metrics_add_rcvd(struct ETCP_CONN* etcp, uint32_t len);
void etcp_metrics_add_loss(struct ETCP_CONN* etcp, uint32_t count);
void etcp_metrics_start_timer(struct ETCP_CONN* etcp);
void etcp_metrics_stop_timer(struct ETCP_CONN* etcp);
#ifdef __cplusplus
}
#endif
+76 -162
View File
@@ -1,197 +1,111 @@
/**
* @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 "../lib/debug_config.h"
#include "../lib/mem.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) return;
struct etcp_cbk_entry* entry = e->ready_cbks;
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
e->ready_cbks = NULL;
if (fn) etcp_conn_add_ready_cbk(e, fn, arg);
}
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
if (!e) return;
struct etcp_cbk_entry* entry = e->up_cbks;
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
e->up_cbks = NULL;
if (fn) etcp_conn_add_up_cbk(e, fn, arg);
}
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
if (!e) return;
struct etcp_cbk_entry* entry = e->down_cbks;
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
e->down_cbks = NULL;
if (fn) etcp_conn_add_down_cbk(e, fn, arg);
}
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
if (!inst) return;
struct etcp_cbk_entry* entry = inst->new_conn_cbks;
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
inst->new_conn_cbks = NULL;
if (fn) etcp_add_new_conn_cbk(inst, fn, 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);
static void etcp_cbk_add_to_chain(struct etcp_cbk_entry** head, etcp_cbk_fn fn, void* arg) {
if (!head || !fn) return;
struct etcp_cbk_entry* e = u_malloc(sizeof(struct etcp_cbk_entry));
if (!e) return;
e->fn = fn; e->arg = arg; e->next = *head;
*head = e;
}
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);
}
// Установить 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;
static void etcp_cbk_remove_from_chain(struct etcp_cbk_entry** head, etcp_cbk_fn fn, void* arg) {
if (!head || !fn) return;
struct etcp_cbk_entry** p = head;
while (*p) {
if ((*p)->fn == fn && (*p)->arg == arg) {
struct etcp_cbk_entry* rm = *p;
*p = rm->next; u_free(rm); return;
}
p = &(*p)->next;
}
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_conn_add_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->ready_cbks, fn, arg); }
void etcp_conn_remove_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->ready_cbks, fn, arg); }
void etcp_conn_add_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->up_cbks, fn, arg); }
void etcp_conn_remove_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->up_cbks, fn, arg); }
void etcp_conn_add_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->down_cbks, fn, arg); }
void etcp_conn_remove_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->down_cbks, fn, arg); }
void etcp_add_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) { if (inst) etcp_cbk_add_to_chain(&inst->new_conn_cbks, fn, arg); }
void etcp_remove_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) { if (inst) etcp_cbk_remove_from_chain(&inst->new_conn_cbks, fn, arg); }
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state) {
if (!conn) return;
conn->routing_exchange_active = new_state;
if (new_state >= 3 && conn->bgp_ready_cbk)
conn->bgp_ready_cbk(conn);
}
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
if (!inst) {
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;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
int result = queue_data_put(pn->input, entry);
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
if (result != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
return -1;
}
return 0;
if (!conn || !entry || !conn->send_input_q) return -1;
return queue_data_put(conn->send_input_q, 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_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
uint8_t id = e->dgram[0];
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) {
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_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
if (inst->api_bindings.callbacks[id])
inst->api_bindings.callbacks[id](conn, e);
else if (inst->api_bindings.callbacks[0])
inst->api_bindings.callbacks[0](conn, e);
else { queue_dgram_free(e); queue_entry_free(e); }
queue_resume_callback(queue);
}
+60 -8
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 - пакет для передачи адресату
@@ -15,25 +17,47 @@
#ifndef ETCP_API_H
#define ETCP_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "../lib/ll_queue.h"
#define ETCP_MAX_BINDINGS 256
// ETCP packet IDs
// === Raw ETCP packet IDs (etcp_bind/etcp_send) ===
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы (BGP)
#define ETCP_ID_SVC_ROUTE 0x03 // Транспорт роутера (etcp_router)
// === Router-сервисы (etcp_router_bind/etcp_route_send) ===
#define ETCP_RT_ID_DATA 0x00 // routing.c — маршрутизация данных
#define ETCP_RT_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_RT_ID_SVC_ROUTE 0x03 // etcp_router — транспорт роутера
#define ETCP_RT_ID_TCP_PROXY 0x04 // TCP proxy (клиент ↔ exit)
#define ETCP_RT_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_RT_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_RT_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
#define ETCP_RT_ID_CONN_MGR 0x11 // Connection Manager — management connections
// Forward declarations
struct ETCP_CONN;
struct UTUN_INSTANCE;
/**
* @brief Тип коллбэка для приёма метрик от пира
* @param user_ptr пользовательский указатель
* @param conn ETCP соединение от которого получены метрики
* @param csv CSV-строка метрик
* @param csv_len длина строки
* @param sig Ed25519 подпись (64 байта) или NULL если ключ неизвестен
*/
typedef void (*etcp_metrics_fn)(void* user_ptr, struct ETCP_CONN* conn,
const uint8_t* csv, uint32_t csv_len,
const uint8_t* sig);
/**
* @brief Тип коллбэка для приёма пакетов
* @param conn ETCP соединение от которого получен пакет
@@ -47,8 +71,19 @@ typedef void (*etcp_recv_fn)(struct ETCP_CONN* conn, struct ll_entry* entry);
// Forward declaration
struct ETCP_CONN;
struct UTUN_INSTANCE;
struct TOPO_NODEQ;
typedef void (*etcp_cbk_fn)(struct ETCP_CONN* conn, void* arg);
// ---- Background connection initialization ----
#define ETCP_CONNECT_EARLY 1
#define ETCP_CONNECT_LATE 2
#define ETCP_CONNECT_BGP_READY 4 // BGP-синхронизация завершена
typedef void (*etcp_connect_callback_t)(void* arg, struct ETCP_CONN* conn, int type);
int etcp_connect(struct UTUN_INSTANCE* instance, struct TOPO_NODEQ* node,
etcp_connect_callback_t cb, void* arg, uint8_t flags);
/**
* @brief Установить callback при создании нового ETCP соединения
*
@@ -63,6 +98,8 @@ void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_
*/
struct ETCP_BINDINGS {
etcp_recv_fn callbacks[ETCP_MAX_BINDINGS]; /**< Массив callbacks, NULL = не установлен */
etcp_metrics_fn on_metrics_rcvd; /**< Callback приёма метрик от пира */
void* metrics_user_ptr; /**< Аргумент для on_metrics_rcvd */
};
/**
@@ -114,6 +151,17 @@ void etcp_conn_set_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
void etcp_conn_set_up_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
void etcp_conn_add_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_conn_remove_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_conn_add_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_conn_remove_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_conn_add_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_conn_remove_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
void etcp_add_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg);
void etcp_remove_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg);
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state);
/**
* @brief Внутренняя функция etcp: Коллбэк для очередей output ll_queue normalizer
*
@@ -128,4 +176,8 @@ void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
*/
void etcp_int_recv(struct ll_queue* queue, void* arg);
#ifdef __cplusplus
}
#endif
#endif // ETCP_API_H
+8
View File
@@ -1,6 +1,10 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define BBR_SCALE 8
#define BBR_UNIT (1 << BBR_SCALE)
#define BW_SCALE 24
@@ -110,3 +114,7 @@ void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
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);
#ifdef __cplusplus
}
#endif
+283
View File
@@ -0,0 +1,283 @@
#include "etcp_api.h"
#include "etcp.h"
#include "utun_instance.h"
#include "topo_node.h"
#include "stcp_link.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include <string.h>
#define DEBUG_CATEGORY_ETCP_CONNECT DEBUG_CATEGORY_ETCP
struct etcp_connect_cb_node {
etcp_connect_callback_t cb;
void* arg;
uint8_t flags;
struct etcp_connect_cb_node* next;
};
struct ETCP_CONNECT {
struct ETCP_CONNECT* next;
struct UTUN_INSTANCE* instance;
uint64_t node_id;
struct ETCP_CONN* conn;
struct etcp_connect_cb_node* cb_list;
void* initial_timer;
void* settle_timer;
struct stcp_link* tcp_link;
uint16_t min_rtt;
uint8_t early_delivered : 1;
uint8_t done : 1;
uint8_t tcp_ready : 1;
};
static void tcp_link_ready_cb(struct stcp_link* link, void* arg);
static void tcp_link_close_cb(struct stcp_link* link, int err, void* arg);
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg);
static struct ETCP_CONNECT* connect_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
struct ETCP_CONNECT* ctx = inst->pending_connects;
while (ctx) { if (ctx->node_id == node_id) return ctx; ctx = ctx->next; }
return NULL;
}
static void connect_deliver(struct ETCP_CONNECT* ctx, int type) {
struct etcp_connect_cb_node** pp = &ctx->cb_list;
while (*pp) {
struct etcp_connect_cb_node* node = *pp;
if (type == 0 || (node->flags & (uint8_t)type)) {
*pp = node->next;
node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type);
u_free(node);
} else {
pp = &node->next;
}
}
}
static void connect_cancel(struct ETCP_CONNECT* ctx) {
if (!ctx) return;
struct UTUN_INSTANCE* inst = ctx->instance;
if (inst) {
struct ETCP_CONNECT** pp = &inst->pending_connects;
while (*pp && *pp != ctx) pp = &(*pp)->next;
if (*pp) *pp = ctx->next;
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
}
u_free(ctx);
}
static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct TOPO_NODEQ* node) {
if (!node || !node->node) return;
for (const struct TOPO_ADDR4* a = node->node->v4_addrs; a; a = a->next) {
if (a->port == 0) continue;
{ int zero = 1; for (int j = 0; j < 4; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue; }
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, a->addr, 4); sin.sin_port = htons(a->port);
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
if (!(a->protocol & TOPO_PROTO_TCP)) {
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET) etcp_link_new(ctx->conn, s, &sa, 0); s = s->next; }
}
if ((a->protocol & TOPO_PROTO_TCP) && !ctx->tcp_link) {
struct stcp_link_config tcp_cfg = {.ua = ctx->instance->ua, .my_keys = &ctx->instance->my_keys, .inst = ctx->instance,
.peer_pubkey = ctx->conn->crypto_ctx.peer_public_key, .peer_pubkey_mode = 0,
.remote_addr = &sa, .remote_port = a->port};
ctx->tcp_link = stcp_link_connect(&tcp_cfg);
if (ctx->tcp_link) { ctx->conn->transport_link = (void*)ctx->tcp_link; stcp_link_set_on_ready(ctx->tcp_link, tcp_link_ready_cb, ctx); stcp_link_set_on_close(ctx->tcp_link, tcp_link_close_cb, ctx); }
}
}
}
static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct TOPO_NODEQ* node) {
if (!node || !node->node) return;
for (const struct TOPO_ADDR6* a = node->node->v6_addrs; a; a = a->next) {
if (a->port == 0) continue;
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue;
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, a->addr, 16); sin6.sin6_port = htons(a->port);
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
if (!(a->protocol & TOPO_PROTO_TCP)) {
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET6) etcp_link_new(ctx->conn, s, &sa, 0); s = s->next; }
}
if ((a->protocol & TOPO_PROTO_TCP) && !ctx->tcp_link) {
struct stcp_link_config tcp_cfg = {.ua = ctx->instance->ua, .my_keys = &ctx->instance->my_keys, .inst = ctx->instance,
.peer_pubkey = ctx->conn->crypto_ctx.peer_public_key, .peer_pubkey_mode = 0,
.remote_addr = &sa, .remote_port = a->port};
ctx->tcp_link = stcp_link_connect(&tcp_cfg);
if (ctx->tcp_link) {
ctx->conn->transport_link = (void*)ctx->tcp_link;
stcp_link_set_on_ready(ctx->tcp_link, tcp_link_ready_cb, ctx);
stcp_link_set_on_close(ctx->tcp_link, tcp_link_close_cb, ctx);
}
}
}
}
static void tcp_link_ready_cb(struct stcp_link* link, void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done || ctx->tcp_ready) return;
ctx->tcp_ready = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] TCP link ready for node 0x%016llx", (unsigned long long)ctx->node_id);
connect_ready_cb(ctx->conn, ctx);
}
static void tcp_link_close_cb(struct stcp_link* link, int err, void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] TCP link closed node 0x%016llx err=%d",
(unsigned long long)ctx->node_id, err);
ctx->tcp_link = NULL;
}
static void connect_bgp_ready_cb(struct ETCP_CONN* conn) {
struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id);
if (!ctx || ctx->done) return;
connect_deliver(ctx, ETCP_CONNECT_BGP_READY);
}
static void connect_initial_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->initial_timer = NULL;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] timeout for node 0x%016llx", (unsigned long long)ctx->node_id);
struct ETCP_CONN* conn = ctx->conn; ctx->conn = NULL;
if (ctx->tcp_link) { stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL; }
connect_deliver(ctx, 0);
if (conn) etcp_connection_close(conn);
connect_cancel(ctx);
}
static void connect_settle_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->settle_timer = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] settle for node 0x%016llx, min_rtt=%u",
(unsigned long long)ctx->node_id, ctx->min_rtt);
struct ETCP_LINK* link = ctx->conn->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (!link->initialized) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed link=%p for node 0x%016llx",
(void*)link, (unsigned long long)ctx->node_id);
etcp_link_close(link);
}
link = next;
}
if (ctx->tcp_link && !ctx->tcp_ready) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed TCP link for node 0x%016llx",
(unsigned long long)ctx->node_id);
stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL;
}
etcp_conn_remove_ready_cbk(ctx->conn, connect_ready_cb, ctx);
connect_deliver(ctx, ETCP_CONNECT_LATE);
connect_cancel(ctx);
}
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
struct ETCP_LINK* link = conn->links;
while (link) {
if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt)
ctx->min_rtt = link->rtt_last;
link = link->next;
}
if (ctx->early_delivered) return;
ctx->early_delivered = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] early ready for node 0x%016llx, min_rtt=%u",
(unsigned long long)ctx->node_id, ctx->min_rtt);
connect_deliver(ctx, ETCP_CONNECT_EARLY);
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
uint32_t settle_tb;
if (ctx->min_rtt == 0xFFFF) settle_tb = 20000;
else {
settle_tb = (uint32_t)ctx->min_rtt * 8;
if (settle_tb < 5000) settle_tb = 5000;
if (settle_tb > 20000) settle_tb = 20000;
}
ctx->settle_timer = uasync_set_timeout(ctx->instance->ua, (int)settle_tb, ctx,
connect_settle_timeout_cb, "etcp_connect_settle");
}
int etcp_connect(struct UTUN_INSTANCE* inst, struct TOPO_NODEQ* node,
etcp_connect_callback_t cb, void* arg, uint8_t flags) {
if (!inst || !node || !cb) return -1;
uint64_t node_id = node->node->node_id;
struct ETCP_CONN* existing = inst->connections;
while (existing) {
if (existing->peer_node_id == node_id) {
struct ETCP_LINK* l = existing->links;
while (l) { if (l->link_state == 3) break; l = l->next; }
if (l) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connected, delivering immediately",
(unsigned long long)node_id);
if (flags & ETCP_CONNECT_EARLY) cb(arg, existing, ETCP_CONNECT_EARLY);
if (flags & ETCP_CONNECT_LATE) cb(arg, existing, ETCP_CONNECT_LATE);
if ((flags & ETCP_CONNECT_BGP_READY) && existing->routing_exchange_active >= 3)
cb(arg, existing, ETCP_CONNECT_BGP_READY);
return 0;
}
}
existing = existing->next;
}
struct ETCP_CONNECT* ctx = connect_find(inst, node_id);
if (ctx) {
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) return -1;
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connecting, added cb",
(unsigned long long)node_id);
return 0;
}
struct ETCP_CONN* conn = etcp_connection_create(inst, NULL);
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] failed to create ETCP_CONN for node 0x%016llx",
(unsigned long long)node_id); cb(arg, NULL, 0); return -1; }
conn->next = inst->connections; inst->connections = conn; inst->connections_count++;
if (sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_init_ctx failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
if (sc_set_peer_public_key(&conn->crypto_ctx, node->node->public_key, 0) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_set_peer_public_key failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] alloc failed");
etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
ctx->min_rtt = 0xFFFF;
{
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) { u_free(ctx); etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
}
etcp_conn_add_ready_cbk(conn, connect_ready_cb, ctx);
conn->bgp_ready_cbk = connect_bgp_ready_cb;
connect_create_links_v4(ctx, node);
connect_create_links_v6(ctx, node);
if (!conn->links && !conn->transport_link) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx",
(unsigned long long)node_id);
if (ctx->tcp_link) { stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL; }
connect_deliver(ctx, 0);
etcp_connection_close(conn); u_free(ctx); return -1;
}
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx, connect_initial_timeout_cb, "etcp_connect_init");
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
return 0;
}
+677 -551
View File
File diff suppressed because it is too large. Load diff
+51 -3
View File
@@ -1,6 +1,11 @@
#ifndef ETCP_CONNECTIONS_H
#define ETCP_CONNECTIONS_H
#ifdef __cplusplus
extern "C" {
#endif
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
@@ -54,6 +59,46 @@ 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)
// V3 fields:
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 29: client Ed25519 pubkey (32 bytes)
} __attribute__((packed));
#define ETCP_INIT_REQ_SIZE sizeof(struct ETCP_INIT_REQUEST_PKT)
// 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)
// V3 fields:
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 25: server Ed25519 pubkey (32 bytes)
} __attribute__((packed));
#define ETCP_INIT_RESP_SIZE sizeof(struct ETCP_INIT_RESPONSE_PKT)
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 {
@@ -106,6 +151,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
@@ -147,6 +193,7 @@ struct ETCP_LINK {
uint8_t remote_only_local; // only_local flag from peer
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
uint8_t remote_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT handshake)
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
@@ -171,9 +218,6 @@ struct ETCP_LINK {
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
/* Биты блокировки отправки по причине (для корректного resume) */
unsigned send_blocked_inflight : 1;
// statistics
size_t encrypt_errors;
size_t decrypt_errors;
@@ -297,4 +341,8 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
#ifdef __cplusplus
}
#endif
#endif // ETCP_CONNECTIONS_H
+9
View File
@@ -1,6 +1,11 @@
#ifndef ETCP_DEBUG_H
#define ETCP_DEBUG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "etcp_connections.h"
typedef struct { char a[16]; } IP_STR;
@@ -9,4 +14,8 @@ IP_STR ip_to_string(uint32_t ip);
void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int is_send);
#ifdef __cplusplus
}
#endif
#endif
+1 -2
View File
@@ -161,9 +161,8 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* inflight (BBR) */
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
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->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
link->bbr_pacing_rate);
+9
View File
@@ -1,10 +1,19 @@
#ifndef ETCP_DUMP_H
#define ETCP_DUMP_H
#ifdef __cplusplus
extern "C" {
#endif
#include "etcp.h"
#include "utun_instance.h"
void etcp_dump_conn_state(struct ETCP_CONN* conn);
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance);
#ifdef __cplusplus
}
#endif
#endif
+6 -18
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;
}
@@ -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);
+881 -228
View File
File diff suppressed because it is too large. Load diff
+110 -6
View File
@@ -9,34 +9,73 @@
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "etcp_api.h"
#define TRANSIT_QUEUE_HASH 256
#pragma pack(push, 1)
struct SVC_ROUTE_HDR {
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03) / ETCP_RT_ID_SVC_ROUTE
uint64_t dst_node_id;
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
uint8_t svc_id; // идентификатор сервиса
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
uint16_t timestamp; // часы отправителя (0.1ms), для RTT; ACK: эхо последнего data-пакета
};
#pragma pack(pop)
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 25
#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_FLAG_CLOSE 0x02 // нормальное закрытие conn
#define ROUTER_SESS_ID_SHIFT 4
#define ROUTER_SESS_ID_MASK 0x30
// Очередь транзитных пакетов для пары (src_node_id, dst_node_id)
// Создаётся когда не получается отправить пакет в ETCP напрямую,
// удаляется когда очередь становится пустой.
// ll.data[0..15] = src:8 + dst:8 — совпадает с полями ниже по памяти
struct TRANSIT_QUEUE {
struct ll_entry ll; // data[0..15] = src_node_id:8 + dst_node_id:8 (16-byte hash index)
uint64_t src_node_id; // = data[0..7]
uint64_t dst_node_id; // = data[8..15]
struct ll_queue* q; // FIFO транзитных пакетов (ll_entry с dgram), начиная с data[16]
struct queue_waiter_handle waiter; // backpressure waiter на bp-очередь (normalizer->input или tx_queue)
struct ETCP_CONN* conn; // next_hop (для etcp_send в drain_cb)
};
// Состояние одного логического подключения (remote_node_id + svc_id)
struct ETCP_ROUTER_CONN {
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
uint64_t remote_node_id; // = data[0..7]
uint8_t svc_id; // = data[8]
uint16_t last_dgram_ts; // последний timestamp датаграммы (rx или tx, timebase 0.1ms)
uint16_t rtt; // измеренный RTT (0.1ms)
int32_t rtt_jitter; // EMA jitter, fixed-point *65536 (как в ETCP)
uint16_t max_inflight; // настраиваемый рабочий максимум (default=ROUTER_MAX_INFLIGHT)
uint16_t inflight_limit; // текущий эффективный лимит (=max_inflight или MINRTT_PROBE_MAX_INFLIGHT)
uint16_t minrtt; // скользящее среднее minRTT (0.1ms), default=100 (10ms)
uint16_t minrtt_window[10]; // окно 10 последних замеров minRTT
uint64_t minrtt_window_tb[10];// таймстемпы замеров (timebase 0.1ms)
uint8_t minrtt_window_idx; // cyclic index 0..9
uint8_t minrtt_window_count; // активных записей в окне (0..10)
uint8_t minrtt_probe; // режим измерения (inflight capped at 4)
uint64_t last_inflight_loaded_tb; // когда inflight >= threshold
uint64_t last_inflight_unloaded_tb; // когда inflight < threshold
uint8_t inflight_was_loaded; // 1=канал загружен
uint16_t last_recv_pkt_ts; // timestamp пира из последнего data-пакета
uint64_t last_recv_pkt_local_tb; // наше время приёма последнего data-пакета (0.1ms timebase)
uint8_t last_recv_updated; // флаг: есть данные для эха в ACK
uint32_t tx_seq; // следующий seq для отправки
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
@@ -52,9 +91,36 @@ struct ETCP_ROUTER_CONN {
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
void* send_resume_timer; // таймер возобновления отправки
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
uint8_t no_route; // 1 = нет BGP-маршрута, передача приостановлена
void* no_route_timer; // таймер 20ms проверки появления маршрута
uint8_t no_ack_count; // счётчик последовательных ретрансмиссий без ACK
uint8_t closed; // 1 = в процессе закрытия, таймеры игнорируют
void* close_callback; // пользовательский коллбэк завершения закрытия
void* close_callback_arg; // аргумент для close_callback
uint8_t sess_id; // наш session id (0-3)
uint8_t peer_sess_id; // последний sess_id от peer'а
uint8_t start_sent; // 0 = нужно отправить START в первом data
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
// Ретрансмиты: inflight очередь
struct ll_queue* inflight_q; // хеш по seq (4 байта), непрерывный блок без SACK
void* retrans_timer; // таймер проверки застоя ACK
uint64_t last_ack_changed_tb; // когда tx_acked последний раз менялся (0.1ms)
// Очередь приёма входящего трафика (между сетью и recv_q)
struct ll_queue* incoming_q; // FIFO очередь входящих пакетов
uint8_t incoming_data_ready; // 1 = данные в incoming_q и callback был вызван
uint32_t c_pkts_sent; // успешные отправки данных
uint32_t c_pkts_send_err; // ошибки отправки
uint32_t c_pkts_rcvd; // получено и доставлено данных
uint32_t c_ack_sent; // отправлено ACK
uint32_t c_ack_recv; // получено ACK (последовательных)
uint32_t c_retrans_done; // число выполненых ретрансмитов
uint32_t c_dup_dropped; // дропнуто дубликатов seq
uint32_t c_oob_dropped; // дропнуто out-of-bounds seq
uint32_t c_stale_ack; // устаревших ACK
uint32_t c_sign_fail; // signature verification failures
};
#define ROUTER_CONN_HASH_SIZE 256
@@ -63,11 +129,30 @@ struct ETCP_ROUTER_CONN {
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
#define ROUTER_MAX_SEND_Q_PACKETS 64 // порог backpressure на send_q
// Флаги в старших битах seq (взаимоисключающие)
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
// Ретрансмиты
#define ROUTER_RETRANS_TIMEOUT_TB 3000 // 300ms в timebase (0.1ms)
#define ROUTER_INFLIGHT_HASH_SIZE 1024
#define ROUTER_NO_ROUTE_RETRY_TB 200 // 20ms проверка появления маршрута
#define ROUTER_NO_ACK_MAX_RETRANS 17 // 17 × 300ms ≈ 5s таймаут без ACK
// minRTT measurement
#define MINRTT_UNLOADED_MIN_TB 500 // 50ms минимальное время разгрузки канала
#define MINRTT_PROBE_TIMEOUT_TB 100000 // 10 сек без свежих замеров → probe
#define MINRTT_PROBE_MAX_INFLIGHT 4 // лимит inflight в режиме probe
#define MINRTT_WINDOW_SIZE 10
#define MINRTT_DEFAULT_TB 100 // default minRTT = 10ms (заменяется первым RTT)
// Inflight запись — копия отправленного пакета для возможного ретрансмита
struct ROUTER_INFLIGHT {
struct ll_entry ll; // индекс по seq (4 байта, offset 0)
uint32_t seq;
uint64_t last_sent_tb; // время последней отправки (0.1ms)
uint8_t send_count; // число переотправок
uint8_t* payload; // копия данных
size_t payload_len;
};
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
struct ETCP_ROUTER_BINDINGS {
@@ -96,9 +181,21 @@ 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);
// Установить рабочий max_inflight, пересчитывает inflight_limit и state machine
void router_set_max_inflight(struct ETCP_ROUTER_CONN* rconn, uint16_t new_max);
// Асинхронное закрытие: коллбэк вызывается после освобождения памяти rconn
void etcp_router_conn_close_async(struct ETCP_ROUTER_CONN* rconn,
void (*on_close_done)(void* arg),
void* close_arg);
// Закрыть все router_conn для указанного remote_node_id (peer умер)
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
@@ -124,4 +221,11 @@ void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id,
// Очищает 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);
// Удалить все транзитные очереди соединения (вызывается при закрытии ETCP_CONN)
void etcp_router_transit_queues_destroy(struct ETCP_CONN* conn);
#ifdef __cplusplus
}
#endif
#endif // ETCP_ROUTER_H
+1 -1
View File
@@ -32,7 +32,7 @@ int fw_load_rules(struct firewall_ctx *ctx, const struct global_config *cfg) {
if (cfg->firewall_rule_count > 0) {
ctx->rules = u_calloc(cfg->firewall_rule_count, sizeof(struct CFG_FIREWALL_RULE));
if (!ctx->rules) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "fw_load_rules: failed to allocate rules");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate rules");
return -1;
}
memcpy(ctx->rules, cfg->firewall_rules,
+9
View File
@@ -2,6 +2,11 @@
#ifndef LWIP_PBUF_H
#define LWIP_PBUF_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
@@ -38,4 +43,8 @@ int pbuf_take(struct pbuf *p, const void *data, uint16_t len);
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset);
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset);
#ifdef __cplusplus
}
#endif
#endif
+4 -4
View File
@@ -458,7 +458,7 @@ struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
lpcb = (struct tcp_pcb_listen *)memory_pool_alloc(pcb->ctx->pcb_listen_pool);
if (lpcb == NULL) {
pcb->ctx->stats.pcb_listen_fails++;
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen: out of memory");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "out of memory");
return NULL;
}
memset(lpcb, 0, sizeof(*lpcb));
@@ -1097,7 +1097,7 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
if (pcb == NULL) {
ctx->stats.pcb_fails++;
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_alloc: out of memory after all retries");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "out of memory after all retries");
}
}
}
@@ -1116,7 +1116,7 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
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);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
pcb->tmr = ctx->ticks;
pcb->last_timer = ctx->slowtmr_ctr;
pcb->ssthresh = TCP_SND_BUF;
@@ -1167,7 +1167,7 @@ void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
void tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval)
{
if (!pcb || pcb->state == LISTEN) return;
pcb->poll = poll;
pcb->pollfn = poll;
pcb->pollinterval = interval;
}
+10 -1
View File
@@ -2,6 +2,11 @@
#ifndef LWIP_TCP_H
#define LWIP_TCP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "lwip_pbuf.h"
@@ -164,7 +169,7 @@ struct tcp_pcb {
tcp_sent_fn sent;
tcp_recv_fn recv;
tcp_connected_fn connected;
tcp_poll_fn poll;
tcp_poll_fn pollfn;
tcp_err_fn errf;
uint32_t keep_idle;
uint8_t persist_cnt;
@@ -270,4 +275,8 @@ void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval);
#define tcp_clear_flags(pcb, clr_flags) ((pcb)->flags &= (tcpflags_t)~(clr_flags))
#define tcp_is_flag_set(pcb, flag) (((pcb)->flags & (flag)) != 0)
#ifdef __cplusplus
}
#endif
#endif
+9 -9
View File
@@ -67,7 +67,7 @@ static struct tcp_seg *tcp_seg_copy_local(struct tcp_pcb *pcb, struct tcp_seg *s
struct tcp_seg *cseg;
if (!seg || !pcb || !pcb->ctx) return NULL;
cseg = (struct tcp_seg *)memory_pool_alloc(pcb->ctx->seg_pool);
if (!cseg) { pcb->ctx->stats.seg_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_seg_copy_local: no memory"); return NULL; }
if (!cseg) { pcb->ctx->stats.seg_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "no memory"); return NULL; }
memcpy(cseg, seg, sizeof(struct tcp_seg));
pbuf_ref(cseg->p);
return cseg;
@@ -139,14 +139,14 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
uint16_t wire_dst_port = tcphdr->dest;
if (p->len < TCP_HLEN) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: short packet (%u bytes) discarded", p->tot_len);
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "short packet (%u bytes) discarded", p->tot_len);
pbuf_free(p);
return;
}
hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr);
if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: invalid header length (%u)", (unsigned)hdrlen_bytes);
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "invalid header length (%u)", (unsigned)hdrlen_bytes);
pbuf_free(p);
return;
}
@@ -163,7 +163,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
opt2len = (uint16_t)(tcphdr_optlen - tcphdr_opt1len);
pbuf_header(p, -(int16_t)tcphdr_opt1len);
if (opt2len > p->next->len) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: options overflow second pbuf");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "options overflow second pbuf");
pbuf_free(p);
return;
}
@@ -184,7 +184,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
if (flags & (TCP_FIN | TCP_SYN)) {
tcplen++;
if (tcplen < p->tot_len) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: length overflow");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "length overflow");
pbuf_free(p);
return;
}
@@ -314,7 +314,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
if (err == LERR_ABRT) goto aborted;
if (err != LERR_OK) {
pcb->refused_data = recv_data;
DEBUG_INFO(DEBUG_CATEGORY_ALL, "tcp_input: keep incoming packet, pcb is full");
DEBUG_INFO(DEBUG_CATEGORY_ALL, "keep incoming packet, pcb is full");
}
}
@@ -386,7 +386,7 @@ static void tcp_listen_input(struct tcp_pcb_listen *pcb)
(unsigned)tcphdr->src, (unsigned)tcphdr->dest);
npcb = tcp_alloc(pcb->ctx, pcb->prio);
if (npcb == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen_input: could not allocate PCB");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "could not allocate PCB");
if (pcb->accept) pcb->accept(pcb->callback_arg, NULL, LERR_MEM);
return;
}
@@ -1043,7 +1043,7 @@ static void tcp_parseopt(struct tcp_pcb *pcb)
case TCP_OPT_MSS:
if (tcp_get_next_optbyte() != TCP_OPT_MSS_LEN ||
(tcp_optidx - 2 + TCP_OPT_MSS_LEN) > tcphdr_optlen) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_parseopt: bad MSS option length");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "bad MSS option length");
return;
}
mss = (uint16_t)(tcp_get_next_optbyte() << 8);
@@ -1053,7 +1053,7 @@ static void tcp_parseopt(struct tcp_pcb *pcb)
default:
data = tcp_get_next_optbyte();
if (data < 2) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_parseopt: bad option length");
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "bad option length");
return;
}
tcp_optidx += data - 2;
Loaded 100 of 627 files, more files were not shown because too many files have changed in this diff. Show more