@ -44,6 +44,7 @@ static void input_send_q_cb(struct ll_queue* q, void* arg);
static void wait_ack_cb ( struct ll_queue * q , void * arg ) ;
static void send_ack_req_cb ( struct ll_queue * q , void * arg ) ;
static void etcp_conn_process_send_queue ( struct ETCP_CONN * etcp ) ;
static void etcp_connection_free_deferred ( void * arg ) ;
struct ETCP_DGRAM * etcp_request_pkt ( struct ETCP_CONN * etcp ) ;
static void clear_queue ( struct ll_queue * q ) {
@ -217,6 +218,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp - > links_up = 0 ;
etcp - > reset_done = 0 ;
etcp - > callbacks_running = 0 ;
etcp - > ref_count = 0 ;
etcp - > last_rr_link = NULL ;
etcp - > name = u_strdup ( name ) ;
// Initialize log_name with local node_id (peer will be updated later when known)
@ -256,7 +258,19 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp - > link_ready_for_send_fn = etcp_link_ready_callback ;
DEBUG_DEBUG ( DEBUG_CATEGORY_ETCP , " [%s] connection initialized. ETCP=%p mtu=%d, next_tx_id=%u " ,
// Add to instance's connections queue (peer_node_id=0 for pending, reindexed later)
{
struct ll_entry * qe = queue_entry_new ( sizeof ( struct conn_queue_entry ) ) ;
if ( ! qe ) { etcp_connection_close ( etcp ) ; return NULL ; }
struct conn_queue_entry * ce = ( struct conn_queue_entry * ) qe - > data ;
ce - > peer_node_id = 0 ;
ce - > conn = etcp ;
etcp - > conn_queue_entry = qe ;
etcp - > conn_queue = instance - > connections ;
queue_data_put_with_index ( instance - > connections , qe ) ;
}
DEBUG_DEBUG ( DEBUG_CATEGORY_ETCP , " [%s] connection initialized. ETCP=%p mtu=%d, next_tx_id=%u state=pending " ,
etcp - > log_name , etcp , etcp - > mtu , etcp - > next_tx_id ) ;
// Вызываем callback для нового соединения если установлен
@ -286,57 +300,15 @@ static void etcp_on_down(struct ETCP_CONN* etcp) {
}
// Close connection with NULL pointer safety (prevents double free)
void etcp_connection_close ( struct ETCP_CONN * etcp ) {
DEBUG_TRACE ( DEBUG_CATEGORY_ETCP , " " ) ;
// Phase 2 resources cleanup (callable both sync and async via call_soon)
static void etcp_connection_free_resources ( struct ETCP_CONN * etcp ) {
if ( ! etcp ) return ;
DEBUG_DEBUG ( DEBUG_CATEGORY_ETCP , " [%s] freeing resources phase 2 " , etcp - > log_name ) ;
if ( etcp - > callbacks_running ) {
DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " [%s] FATAL: etcp_connection_close called from inside callback chain — SEGFAULTING to show backtrace " ,
etcp - > log_name ) ;
* ( volatile int * ) 0 = 0 ;
}
if ( etcp - > links_up ! = 0 ) {
etcp - > links_up = 0 ;
etcp_on_down ( etcp ) ;
}
// Cancel active timers to prevent memory leaks
if ( etcp - > retrans_timer ) {
uasync_cancel_timeout ( etcp - > instance - > ua , etcp - > retrans_timer ) ;
etcp - > retrans_timer = NULL ;
}
if ( etcp - > ack_resp_timer ) {
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: 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 ) ;
etcp - > normalizer = NULL ;
}
// Clear links BEFORE draining queues: etcp_link_close calls
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
// which accesses input_send_q, input_queue, input_wait_ack.
// Close links
if ( etcp - > links ) {
struct ETCP_LINK * link = etcp - > links ;
while ( link ) {
struct ETCP_LINK * next = link - > next ;
etcp_link_close ( link ) ;
link = next ;
}
while ( link ) { struct ETCP_LINK * next = link - > next ; etcp_link_close ( link ) ; link = next ; }
etcp - > links = NULL ;
}
@ -350,35 +322,86 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
// 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 ; }
{ 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 ) ;
etcp - > inflight_pool = NULL ;
}
if ( etcp - > inflight_pool ) { memory_pool_destroy ( etcp - > inflight_pool ) ; etcp - > inflight_pool = NULL ; }
if ( etcp - > io_pool ) { memory_pool_destroy ( etcp - > io_pool ) ; etcp - > io_pool = NULL ; }
u_free ( etcp - > name ) ;
u_free ( etcp ) ;
}
static void etcp_connection_free_deferred ( void * arg ) {
etcp_connection_free_resources ( ( struct ETCP_CONN * ) arg ) ;
}
// Close connection: phase 1 detach + deferred phase 2 cleanup
void etcp_connection_close ( struct ETCP_CONN * etcp ) {
DEBUG_TRACE ( DEBUG_CATEGORY_ETCP , " " ) ;
if ( ! etcp ) return ;
if ( etcp - > state = = 2 ) { DEBUG_WARN ( DEBUG_CATEGORY_ETCP , " [%s] already deleted " , etcp - > log_name ) ; return ; }
if ( etcp - > io_pool ) {
memory_pool_destroy ( etcp - > io_pool ) ;
etcp - > io_pool = NULL ;
if ( etcp - > callbacks_running ) {
DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " [%s] FATAL: etcp_connection_close called from inside callback chain — SEGFAULTING to show backtrace " ,
etcp - > log_name ) ;
* ( volatile int * ) 0 = 0 ;
}
u_free ( etcp - > name ) ;
// === PHASE 1: detach from external world ===
{ 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 - - ; }
}
if ( etcp - > links_up ! = 0 ) { etcp - > links_up = 0 ; etcp_on_down ( etcp ) ; }
// Cancel active timers
if ( etcp - > retrans_timer ) { uasync_cancel_timeout ( etcp - > instance - > ua , etcp - > retrans_timer ) ; etcp - > retrans_timer = NULL ; }
if ( etcp - > ack_resp_timer ) { uasync_cancel_timeout ( etcp - > instance - > ua , etcp - > ack_resp_timer ) ; etcp - > ack_resp_timer = NULL ; }
etcp_metrics_stop_timer ( etcp ) ;
routing_del_conn ( etcp ) ;
etcp_router_transit_queues_destroy ( etcp ) ;
if ( etcp - > normalizer ) { pn_deinit ( ( struct PKTNORM * ) etcp - > normalizer ) ; etcp - > normalizer = NULL ; }
// Close links (detach from socket scan queues)
if ( etcp - > links ) {
struct ETCP_LINK * link = etcp - > links ;
while ( link ) { struct ETCP_LINK * next = link - > next ; etcp_link_close ( link ) ; link = next ; }
etcp - > links = NULL ;
}
// Clear next pointer to prevent dangling references
etcp - > next = NULL ;
// Remove from instance queue
if ( etcp - > conn_queue & & etcp - > conn_queue_entry ) {
queue_remove_data ( etcp - > conn_queue , etcp - > conn_queue_entry ) ;
queue_entry_free ( etcp - > conn_queue_entry ) ;
etcp - > conn_queue_entry = NULL ;
etcp - > conn_queue = NULL ;
}
// TODO: Free rx_list, etc.
u_free ( etcp ) ;
etcp - > state = 2 ; // deleted
// === PHASE 2: deferred resource cleanup (only if no outstanding refs) ===
if ( etcp - > ref_count = = 0 )
uasync_call_soon ( etcp - > instance - > ua , etcp , etcp_connection_free_deferred ) ;
// else: cleanup deferred until last etcp_conn_ref_free()
}
// Take a reference on connection. Returns -1 if already deleted (state==2).
int etcp_conn_ref_take ( struct ETCP_CONN * conn ) {
if ( ! conn ) return - 1 ;
if ( conn - > state = = 2 ) { DEBUG_WARN ( DEBUG_CATEGORY_ETCP , " [%s] ref_take on deleted conn " , conn - > log_name ) ; return - 1 ; }
conn - > ref_count + + ;
DEBUG_DEBUG ( DEBUG_CATEGORY_ETCP , " [%s] ref_take ref_count=%d " , conn - > log_name , conn - > ref_count ) ;
return 0 ;
}
// Release a reference. If last ref and conn is deleted, schedule deferred free.
void etcp_conn_ref_free ( struct ETCP_CONN * conn ) {
if ( ! conn ) return ;
if ( conn - > ref_count < = 0 ) { DEBUG_WARN ( DEBUG_CATEGORY_ETCP , " [%s] ref_free underflow ref_count=%d " , conn - > log_name , conn - > ref_count ) ; return ; }
conn - > ref_count - - ;
DEBUG_DEBUG ( DEBUG_CATEGORY_ETCP , " [%s] ref_free ref_count=%d state=%d " , conn - > log_name , conn - > ref_count , conn - > state ) ;
if ( conn - > ref_count = = 0 & & conn - > state = = 2 )
uasync_call_soon ( conn - > instance - > ua , conn , etcp_connection_free_deferred ) ;
}
// Reset connection
@ -543,24 +566,77 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме
// внутренняя функция. Вызывается один раз когда первый линк готов.
void etcp_conn_ready ( struct ETCP_CONN * conn ) {
if ( ! conn ) return ;
if ( conn - > initialized ) return ; // already ready
conn - > initialized = 1 ;
conn - > reset_done = 1 ;
// Если tx_state не установлен (первое подключение без reinit), установим его
if ( conn - > tx_state = = 0 ) {
conn - > tx_state = ETCP_TX_STATE_DATA_WAIT ;
}
if ( conn - > tx_state = = 0 ) { conn - > tx_state = ETCP_TX_STATE_DATA_WAIT ; }
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " [%s] Connection ready " , conn - > log_name ) ;
etcp_conn_queue_set_ready ( conn ) ;
}
// Move from pending to indexed connections, fire ready callbacks
void etcp_conn_queue_set_ready ( struct ETCP_CONN * conn ) {
if ( ! conn | | conn - > state ! = 0 ) return ;
// Reindex: remove old entry (key=0), add new entry with real peer_node_id
if ( conn - > conn_queue & & conn - > conn_queue_entry ) {
queue_remove_data ( conn - > conn_queue , conn - > conn_queue_entry ) ;
queue_entry_free ( conn - > conn_queue_entry ) ;
}
struct ll_entry * qe = queue_entry_new ( sizeof ( struct conn_queue_entry ) ) ;
if ( ! qe ) { DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " [%s] failed to alloc queue entry for ready " , conn - > log_name ) ; return ; }
struct conn_queue_entry * ce = ( struct conn_queue_entry * ) qe - > data ;
ce - > peer_node_id = conn - > peer_node_id ;
ce - > conn = conn ;
conn - > conn_queue_entry = qe ;
conn - > conn_queue = conn - > instance - > connections ;
conn - > state = 1 ;
queue_data_put_with_index ( conn - > instance - > connections , qe ) ;
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " [%s] moved to ready queue, state=%d peer_node_id=0x%llx " ,
conn - > log_name , conn - > state , ( unsigned long long ) conn - > peer_node_id ) ;
etcp_metrics_start_timer ( conn ) ;
// Вызываем callback если установлен
conn - > callbacks_running = 1 ;
{ struct etcp_cbk_entry * cbe = conn - > ready_cbks ; while ( cbe ) { struct etcp_cbk_entry * n = cbe - > next ; cbe - > fn ( conn , cbe - > arg ) ; cbe = n ; } }
if ( conn - > links_up ) etcp_on_up ( conn ) ;
conn - > callbacks_running = 0 ;
}
// Set peer_node_id, reindex if already in connections queue
void etcp_conn_set_peer_node_id ( struct ETCP_CONN * conn , uint64_t peer_node_id ) {
if ( ! conn ) return ;
if ( conn - > state = = 2 ) { DEBUG_WARN ( DEBUG_CATEGORY_ETCP , " [%s] set_peer_node_id on deleted conn " , conn - > log_name ) ; return ; }
if ( conn - > peer_node_id = = peer_node_id ) return ;
if ( conn - > state = = 1 & & conn - > peer_node_id ! = 0 ) {
// Already in indexed queue with old ID — reindex
DEBUG_WARN ( DEBUG_CATEGORY_ETCP , " [%s] peer_node_id changed 0x%llx -> 0x%llx, reindexing " ,
conn - > log_name , ( unsigned long long ) conn - > peer_node_id , ( unsigned long long ) peer_node_id ) ;
if ( conn - > conn_queue & & conn - > conn_queue_entry ) {
queue_remove_data ( conn - > conn_queue , conn - > conn_queue_entry ) ;
queue_entry_free ( conn - > conn_queue_entry ) ;
}
struct ll_entry * qe = queue_entry_new ( sizeof ( struct conn_queue_entry ) ) ;
if ( ! qe ) { DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " [%s] reindex alloc failed " , conn - > log_name ) ; return ; }
struct conn_queue_entry * ce = ( struct conn_queue_entry * ) qe - > data ;
ce - > peer_node_id = peer_node_id ;
ce - > conn = conn ;
conn - > conn_queue_entry = qe ;
conn - > conn_queue = conn - > instance - > connections ;
queue_data_put_with_index ( conn - > instance - > connections , qe ) ;
}
conn - > peer_node_id = peer_node_id ;
etcp_update_log_name ( conn ) ;
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " [%s] peer_node_id set to 0x%llx " , conn - > log_name , ( unsigned long long ) peer_node_id ) ;
}
// Update log_name when peer_node_id becomes known
void etcp_update_log_name ( struct ETCP_CONN * etcp ) {