- Replace db_sync_stub.c with real src/db_sync.c in libutun
- Remove message sync protocol from chat_sync (INIT_SYNC/INIT_RESP/SEND_DATA/PUSH/ACK_PUSH/SYNC_DONE)
- Keep chat_sync auxiliary: join protocol, peer management, auto-connect
- Remove chat_core_insert_record, cursor_*, mark_sent/ttl_delete stubs
- chat_core on_db_sync_insert now only does SQLite INSERT + GUI notify
- Move db_sync_init() from instance_init_common to utun_instance_init for config override timing
- Force db_sync_enabled=1 in utun_node.cpp
- db_sync handles all P2P message sync via service 0x20
Replace etcp_router_bind/etcp_route_send/etcp_router_unbind with
direct etcp_bind/etcp_send/etcp_unbind + find-conn-by-node_id helpers.
Same approach as db_sync (0x20). Service IDs unchanged (0x30, 0x31).
When etcp_conn_reinit is called, it clears ETCP queues (input_wait_ack
etc.) which frees dgram objects to data_pool. If a router retransmit
timer fires concurrently, it sends through the same conn → normalizer
allocates from corrupted data_pool → SIGSEGV.
Fix: call etcp_router_pause_retrans_for_node() BEFORE etcp_conn_reset()
to cancel retrans timers and free inflight_q entries for all router
connections to the peer. No callbacks, no close notifications —
lightweight pause, router connections stay alive and resume when
ETCP connection comes back up.
ca_ready_cb and cc_parallel_ready_cb called etcp_connection_close
synchronously on non-selected connections from within the ready_cbk
callback chain. This corrupts senders_list in topo_group, causing
UAF in topo_group_start_link_nat_check.
Now uses uasync_call_soon (zero-delay timer) to defer the close
to the next event loop iteration, after the callback chain completes
and callbacks_running flag is reset.
Added callbacks_running flag to ETCP_CONN:
- set to 1 before up_cbks / down_cbks / ready_cbks iteration
- reset to 0 after iteration completes
- etcp_connection_close checks flag and does intentional SIGSEGV
with FATAL log message if called during callback chain
This catches illegal synchronous close from within callbacks
instead of producing cryptic UAF segfaults later.
Protocol: added collision field to INIT_REQUEST_PKT (1 byte before ed25519_pubkey).
When two peers connect simultaneously, the one with is_server=0 (API-created
outbound link) is the master. Resolution by smaller node_id:
- We have master link + smaller node_id → send our INIT with collision=1
- We have master link + larger node_id → yield, process as slave
- Remote sends collision=1 → become slave unconditionally
- session_id change always forces reinit (genuine restart)
Removed unconditional etcp_conn_reinit on INIT_RESPONSE(0x03) — guarded by
reset_done flag. Server-side reinit now has collision check BEFORE reset_done
guard; session_id change penetrates reset_done protection.
Closes cross-connect reinit loop causing endless UP/DOWN flapping.
In etcp_conn_ready / etcp_on_up / etcp_on_down / etcp_connection_create /
tcp_server_on_link, callback chains were iterated with:
while (cbe) { cbe->fn(...); cbe = cbe->next; }
If the callback removes itself from the chain (e.g. ca_ready_cb calls
etcp_conn_remove_ready_cbk which u_free's the entry), cbe->next reads
freed memory → SIGSEGV.
Fixed by saving next pointer before invoking the callback:
while (cbe) { n = cbe->next; cbe->fn(...); cbe = n; }
Problem: when two peers connect simultaneously, each incoming
INIT_REQUEST(0x02) or INIT_RESPONSE(0x03) triggers etcp_conn_reinit
unconditionally, causing endless UP/DOWN flapping loop.
Fix: add reset_done flag to ETCP_CONN:
- 0 at creation and after explicit reinit (reinit allowed)
- set to 1 in etcp_conn_ready (connection stable, block reinit)
Three call sites guarded with !conn->reset_done:
- client: handle_init_response_client (INIT_RESPONSE 0x03)
- server: existing link INIT_REQUEST processing
- server: new link INIT_REQUEST processing
send_reset logic preserved unconditionally — only etcp_conn_reinit
itself is blocked when already stable.
- RECV: log every packet (src addr, link found, session_ready, decrypt result)
- RECV: log init decrypt rejection with packet code (catches 0x03 INIT_RESPONSE
silently dropped by init path)
- SEND: log destination addr+fd in send_init_response and etcp_encrypt_send
- REINIT: log full reason in server and client paths (code, session, got_init,
initialized, links_up state before reinit)
- Added struct ca_ctx** ctxs to ca_state (parallel array)
- ca_cleanup now frees all ctxs[i] and the ctxs array
- ca_ready_cb: removed u_free(ctx), added etcp_conn_remove_ready_cbk before cleanup
- ca_timeout_cb: removed u_free(ctx) — ctx lives until ca_cleanup
- All error paths set ctxs[i]=NULL for ca_cleanup safety
etcp_connection_create does NOT add conn to inst->connections list.
Without this, incoming INIT handler can't find the outbound conn
by peer_node_id and creates a new one, causing insert_link collision.
When two peers send INIT simultaneously, the first link occupies the socket.
Incoming INIT arriving after finds the existing outbound link by (ip,port)
instead of failing with 'Can not insert link to socket'.
- ca_timeout_cb: removed etcp_connection_close, ETCP lives past 3s timeout
- ca_state.cancelled flag + chat_core_connect_auto_cancel() for external cancel
- chat_core_connect_auto returns ca_state* via out_state
- Rewritten auto_connect: cursor (ch,peer) instead of bulk collect
- GC every 1s: closes flights with no link_status after 3s
- ac_result_cb no longer clears ca_state (GC handles cleanup)
- stop: cancels all flights via chat_core_connect_auto_cancel
- chat_core_connect_auto: direct ETCP connect from SQLite (no BGP)
- auto_connect: parallel connect to peers with 3s timeout, retry 10s
- RTT saved to node_addresses on disconnect
- vertical status bar + circle badge with online count per channel
- ChannelListView prevents deselection
- Replace get_time_us() with utun_gettimeofday() for PUSH message timestamps
(db_sync.c, chat_core.c) — monotonic clocks differ across Linux/Windows
- Fix merkle_sync: restart session timer in _handle_request to prevent
double-sided timeout when both nodes start member_sync simultaneously
- Fix INIT_RESP short-format: don't mark synced when my_count > peer_count
- Add db_sync_get_last_timestamp() to avoid duplicate timestamp generation
between db_sync and chat_core on_db_sync_insert
- Fix db_sync_stub to generate timestamps consistently
- build.sh: auto-rebuild chatgui via CMake after utun build
chat_sync_push: add chain_hash parameter, use it instead of zeros
(fixes chain_hash mismatch → all PUSH messages were rejected)
on_db_sync_insert: pass computed chain_h to chat_sync_push
cs_handle_channel_join: call member_sync_start for the joiner
(server now initiates member sync when client joins.
Previously merkle_sync was only started by client → timeout
because server never responded)
cs_handle_welcome: add DEBUG_INFO before member_sync_start on client side
chat_sync_push: fix PUSH format to match cs_handle_push expectations
(was missing insert_record header: ts+dh+nid before ct+dlen+data)
Add DEBUG_INFO log with peer count after send.
on_db_sync_insert: call chat_sync_push after successful local insert
→ new messages are now pushed to all connected peers
Add DEBUG_ERROR to 4 silent returns (JSON parse fail, SQL errors)
chat_core_submit_message: add DEBUG_INFO entry log (ch, ct, len)
chat_core_insert_record: add DEBUG_WARN/ERROR to 7 silent returns
(truncated record, SQL prepare fail, unknown step error)
cs_handle_push: add DEBUG_INFO for ACK send, DEBUG_ERROR for insert fail
cs_handle_send_data: DEBUG_WARN when record truncated mid-loop
cs_on_conn_up: DEBUG_INFO sync path with channel count
Full trace now shows every message: SUBMIT → insert → PUSH → RECV PUSH → ACK
cs_on_conn_down: remove invite state zeroing on disconnection.
Only timeout callbacks and explicit protocol end (CS_MSG_ERROR,
transition to JOIN phase) clear the invite state.
Connection flap now causes automatic retry:
conn_up → pending_invite_ch_id != 0 && !info_req_timer → resend
CHANNEL_INFO_REQ. No deadlock possible.
- cs_handle_init_resp: change silent reject len<37→len<5 (actual payload
sent is 6 bytes, plen=5 after stripping type byte).
Support short format (synced, peer_count+sparse_count) and long format
(divergence, with test_pos+peer_ch). Add INFO logs at each decision.
(fixes sync never starting after invite join — RECV INIT_RESP len=5
was silently rejected)
- All 12 silent return branches in handlers: add DEBUG_WARN with
handler name, peer, ch_id, and reason (makes malformed packets
visible in logs)
- Key sync flow INFO logs:
cs_handle_init_sync: peer_count, my_count
cs_handle_send_data: records sent/received count
cs_on_conn_up: invite vs sync path
chat_core_insert_record: successful insert
- on_db_sync_insert: parse JSON {"n":...,"ct":...,"d":...}, store
d→data, n→node_id, ct→content_type with proper chain_hash
(fixes JSON displayed instead of message text)
- chat_core_cursor_open: SELECT chain_hash column
- chat_core_cursor_next: include chain_hash(32) in output record
- cs_handle_send_data: parse chain_hash in received records
(fixes chain_hash mismatch → all sync records were dropped)
- cs_handle_channel_join: post GUI_EVT_MEMBERS_CHANGED
(fixes creator not seeing new member after join)
- cs_handle_welcome: trigger INIT_SYNC with inviter after join
(fixes no message sync after invite join)
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
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
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.
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.
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).
- 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.
- 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
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).
- 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
- 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
- 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