diff --git a/src/transport_layer/etcp.c b/src/transport_layer/etcp.c new file mode 100644 index 00000000..d0c1faa5 --- /dev/null +++ b/src/transport_layer/etcp.c @@ -0,0 +1,2012 @@ +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) + +#include "etcp.h" +#include "etcp_connect.h" +#include "etcp_debug.h" +#include "etcp_loadbalancer.h" +#include "etcp_router.h" +#include "routing.h" +#include "topo_group.h" +#include "route_ping.h" +#include "../lib/u_async.h" +#include "../lib/ll_queue.h" +#include "../lib/debug_config.h" +#include "crc32.h" // For potential hashing, though not used yet. +#include +#include +#include +#include // For bandwidth calcs +#include // For UINT16_MAX +#include // For strftime in metrics snapshot +#include "../lib/mem.h" +#include "../lib/memory_pool.h" + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 + +// Constants from spec (adjusted for completeness) +#define MAX_INFLIGHT_BYTES 65536 // Initial window +#define RETRANS_K1 32 // RTT multiplier for retrans timeout +#define RETRANS_K2 32 // Jitter multiplier +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) +#define BURST_DELAY_FACTOR 4 // Delay before burst +#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans (arbitrary; adjust) +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) + +// Container-of macro for getting struct from data pointer +//#define CONTAINER_OF(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) + +// Forward declarations +static void input_queue_cb(struct ll_queue* q, void* arg); +static void etcp_link_ready_callback(struct ETCP_CONN* etcp); +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); +void etcp_fire_conn_status(struct ETCP_CONN* etcp, int status); +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); + +static void clear_queue(struct ll_queue* q) { + if (!q) return; + struct ll_entry* pkt; + while ((pkt = queue_data_get(q)) != NULL) { + queue_dgram_free(pkt); + queue_entry_free(pkt); + } +} + +static void drain_and_free_queue(struct ll_queue** q) { + if (!*q) return; + clear_queue(*q); + queue_free(*q); + *q = NULL; +} + +static int inflight_seq_cmp(const void* a, const void* b) { + const struct INFLIGHT_PACKET* pa = *(const struct INFLIGHT_PACKET**)a; + const struct INFLIGHT_PACKET* pb = *(const struct INFLIGHT_PACKET**)b; + if (pa->seq < pb->seq) return -1; + if (pa->seq > pb->seq) return 1; + return 0; +} + +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, uint32_t* asm_cap, + uint32_t* returned) { + uint32_t need = (uint32_t)*asm_len + dgram_len; + if (need >= ASM_BUF_MAX_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] assembly buffer would exceed max size %d", etcp->log_name, ASM_BUF_MAX_SIZE); + return; + } + if (need > *asm_cap) { + uint32_t new_cap = *asm_cap ? *asm_cap : 256; + while (new_cap < need) new_cap *= 2; + 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; + } + memcpy(*asm_buf + *asm_len, dgram, dgram_len); + *asm_len = need; + + while (*asm_len >= 2) { + uint16_t pkt_len = (*asm_buf)[0] | ((*asm_buf)[1] << 8); + 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_TOPO_ENTRY) { + struct ll_entry* e = ll_alloc_lldgram(pkt_len); + if (e) { + memcpy(e->dgram, pkt_data, pkt_len); + e->len = pkt_len; + queue_data_put(pn->input, e); + (*returned)++; + } + } + + uint16_t consumed = 2 + pkt_len; + *asm_len -= consumed; + if (*asm_len > 0) memmove(*asm_buf, *asm_buf + consumed, *asm_len); + } +} + +static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) { + struct PKTNORM* pn = etcp->normalizer; + if (!pn) return; + + struct INFLIGHT_PACKET** inflight = NULL; + int inflight_count = 0; + struct ll_entry** input_frags = NULL; + int input_count = 0; + struct ll_entry* entry; + + while ((entry = queue_data_get(etcp->input_wait_ack))) { + inflight = u_realloc(inflight, (inflight_count + 1) * sizeof(*inflight)); + inflight[inflight_count++] = (struct INFLIGHT_PACKET*)entry; + } + while ((entry = queue_data_get(etcp->input_send_q))) { + inflight = u_realloc(inflight, (inflight_count + 1) * sizeof(*inflight)); + inflight[inflight_count++] = (struct INFLIGHT_PACKET*)entry; + } + while ((entry = queue_data_get(etcp->input_queue))) { + input_frags = u_realloc(input_frags, (input_count + 1) * sizeof(*input_frags)); + input_frags[input_count++] = entry; + } + + if (inflight_count + input_count == 0 && (!pn->data || pn->data_ptr == 0)) return; + + if (inflight_count > 0) qsort(inflight, inflight_count, sizeof(*inflight), inflight_seq_cmp); + + uint8_t* asm_buf = NULL; + uint16_t asm_len = 0; + uint32_t asm_cap = 0; + uint32_t returned_packets = 0; + + for (int i = 0; i < inflight_count; i++) { + feed_dgram_to_asm(etcp, pn, inflight[i]->ll.dgram, inflight[i]->ll.len, + &asm_buf, &asm_len, &asm_cap, &returned_packets); + memory_pool_free(etcp->instance->data_pool, inflight[i]->ll.dgram); + memory_pool_free(etcp->inflight_pool, inflight[i]); + } + u_free(inflight); + + for (int i = 0; i < input_count; i++) { + feed_dgram_to_asm(etcp, pn, input_frags[i]->dgram, input_frags[i]->len, + &asm_buf, &asm_len, &asm_cap, &returned_packets); + memory_pool_free(etcp->instance->data_pool, input_frags[i]->dgram); + memory_pool_free(etcp->io_pool, input_frags[i]); + } + u_free(input_frags); + + if (pn->data && pn->data_ptr > 0) { + feed_dgram_to_asm(etcp, pn, pn->data, pn->data_ptr, + &asm_buf, &asm_len, &asm_cap, &returned_packets); + } + + if (asm_buf) { u_free(asm_buf); } + + if (returned_packets > 0) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] returned %u packets to normalizer input during reinit", + etcp->log_name, returned_packets); + } +} + +uint16_t get_current_timestamp() { + return (uint16_t)get_time_tb(); +} + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + if (t1 >= t2) { + return t1 - t2; + } + return (UINT16_MAX - t2) + t1 + 1; +} + +static const char EMPTY_NAME[] = ""; + +// Create new ETCP connection +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* name) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + if (!instance) return NULL; + + + struct ETCP_CONN* etcp = u_calloc(1, sizeof(struct ETCP_CONN)); + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "creating connection failed for instance %p", instance); + return NULL; + } + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue + queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения + etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue + etcp->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 + etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q"); + etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool"); + etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool"); + etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd; + etcp->optimal_inflight=100000; + etcp->initialized=0; + etcp->links_up=0; + etcp->setup_start_tb = get_time_tb(); + 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) + 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->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, "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; + } + + + // etcp->window_size = MAX_INFLIGHT_BYTES; // Not used + etcp->mtu = ETCP_RFC791_MIN_MTU; // Default MTU per RFC 791 + etcp->next_tx_id = 1; + etcp->rtt_avg_10 = 10; // Initial guess (1ms) + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + + + etcp->normalizer = pn_init(etcp); + if (!etcp->normalizer) { + 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); + queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp); + queue_set_callback(etcp->input_wait_ack, wait_ack_cb, etcp); +// queue_set_callback(etcp->ack_q, send_ack_req_cb, etcp);// отправляем через таймаут + + etcp->link_ready_for_send_fn = etcp_link_ready_callback; + + // 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 для нового соединения если установлен + if (instance) { + struct etcp_inst_cbk_entry* cbe = instance->new_conn_cbks; + while (cbe) { struct etcp_inst_cbk_entry* n = cbe->next; cbe->fn(etcp, cbe->arg); cbe = n; } + } + etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_NEW); + + return etcp; +} + + +void etcp_fire_conn_status(struct ETCP_CONN* etcp, int status) { + if (!etcp || !etcp->instance) return; + static const char* names[] = { "NEW", "UP", "DOWN", "DELETE" }; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "[%s] Connection status changed to %s", etcp->log_name, + (status >= 0 && status < (int)(sizeof(names)/sizeof(names[0]))) ? names[status] : "?"); + struct etcp_status_cbk_entry* cbe = etcp->instance->conn_status_cbks; + while (cbe) { struct etcp_status_cbk_entry* n = cbe->next; cbe->fn(etcp, status, cbe->arg); cbe = n; } +} + +void etcp_cbk_fire(struct ETCP_CONN* conn, int event) { + if (!conn) return; + static const char* names[] = { "INIT", "REINIT", "UP", "DOWN", "NODE_CHANGED" }; + int idx = 0, e = event; while (e >>= 1) idx++; + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] callback event: %s", conn->log_name, (idx >= 0 && idx < (int)(sizeof(names)/sizeof(names[0]))) ? names[idx] : "?"); + conn->callbacks_running = 1; + struct etcp_cbk_entry* cbe = conn->cbks; + while (cbe) { struct etcp_cbk_entry* n = cbe->next; if (cbe->event_mask & event) cbe->fn(conn, event, cbe->arg); cbe = n; } + conn->callbacks_running = 0; +} + +static void etcp_on_up(struct ETCP_CONN* etcp) { + int total_links = 0; for (struct ETCP_LINK* l = etcp->links; l; l = l->next) total_links++; + uint64_t elapsed_ms = (get_time_tb() - etcp->setup_start_tb) / 10; + char links_str[256] = {0}; int pp = 0; + for (struct ETCP_LINK* l = etcp->links; l; l = l->next) { + if (l->link_status) { + if (pp) pp += snprintf(links_str + pp, sizeof(links_str) - pp, ", "); + pp += snprintf(links_str + pp, sizeof(links_str) - pp, "%s", sockaddr_storage_to_str(&l->remote_addr).str); + } + } + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Connection UP (%d/%d links, mtu=%d, reinit=%u)%s%s", + etcp->log_name, etcp->links_up, total_links, etcp->mtu, etcp->reinit_count, + pp > 0 ? " — up: " : "", links_str); + (void)elapsed_ms; + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CRYPTO_ON_UP_BEFORE: log=%s seskey=%02x%02x%02x%02x peer_pub=%02x%02x%02x%02x", + etcp->log_name, + etcp->crypto_ctx.session_key[0], etcp->crypto_ctx.session_key[1], + etcp->crypto_ctx.session_key[2], etcp->crypto_ctx.session_key[3], + etcp->crypto_ctx.peer_public_key[0], etcp->crypto_ctx.peer_public_key[1], + etcp->crypto_ctx.peer_public_key[2], etcp->crypto_ctx.peer_public_key[3]); + etcp_cbk_fire(etcp, ETCP_CBK_EVENT_UP); + etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_UP); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CRYPTO_ON_UP_AFTER: log=%s seskey=%02x%02x%02x%02x peer_pub=%02x%02x%02x%02x", + etcp->log_name, + etcp->crypto_ctx.session_key[0], etcp->crypto_ctx.session_key[1], + etcp->crypto_ctx.session_key[2], etcp->crypto_ctx.session_key[3], + etcp->crypto_ctx.peer_public_key[0], etcp->crypto_ctx.peer_public_key[1], + etcp->crypto_ctx.peer_public_key[2], etcp->crypto_ctx.peer_public_key[3]); +} + +static void etcp_on_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link) { + char links_str[256] = {0}; int pp = 0; + for (struct ETCP_LINK* l = etcp->links; l; l = l->next) { + if (l == down_link) continue; + if (pp) pp += snprintf(links_str + pp, sizeof(links_str) - pp, ", "); + pp += snprintf(links_str + pp, sizeof(links_str) - pp, "%s", sockaddr_storage_to_str(&l->remote_addr).str); + } + if (down_link) + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Connection DOWN — link %s lost%s%s", + etcp->log_name, sockaddr_storage_to_str(&down_link->remote_addr).str, + pp > 0 ? ", remaining: " : "", links_str); + else + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Connection DOWN — closing, links: %s", etcp->log_name, pp ? links_str : "(none)"); + etcp_cbk_fire(etcp, ETCP_CBK_EVENT_DOWN); + etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_DOWN); +} + + +// 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); + + // 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; } + etcp->links = NULL; + } + + // Drain and free all queues + drain_and_free_queue(&etcp->input_queue); + drain_and_free_queue(&etcp->output_queue); + drain_and_free_queue(&etcp->input_send_q); + drain_and_free_queue(&etcp->input_wait_ack); + drain_and_free_queue(&etcp->recv_q); + drain_and_free_queue(&etcp->ack_q); + + // Free callback chain + { struct etcp_cbk_entry* cbe = etcp->cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->cbks = 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; } + + etcp_connect_cancel_for_conn(etcp->instance, etcp); + + 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->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; + } + + // === PHASE 1: detach from external world === + + if (etcp->links_up != 0) { etcp->links_up = 0; etcp_on_down(etcp, NULL); } + + // 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; + } + + // 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; + } + + etcp->state = 2; // deleted + + etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_DELETE); + + // === 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 +void etcp_conn_reset(struct ETCP_CONN* etcp) { + // Reset IDs + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Resetting ETCP instance [%s]", etcp->log_name); + etcp->next_tx_id = 1; + etcp->last_rx_id = 0; + etcp->last_delivered_id = 0; + etcp->rx_ack_till = 0; + + // Устанавливаем флаг ожидания первого пакета + etcp->got_initial_pkt = 0; + + // Reset metrics + etcp->unacked_bytes = 0; + etcp->rtt_last = 0; + etcp->rtt_avg_10 = 0; + etcp->jitter = 0; + etcp->last_rtt_cb_time = 0; + etcp->bytes_sent_total = 0; + etcp->retransmissions_count = 0; + etcp->ack_packets_count = 0; + etcp->rx_dup_count = 0; + etcp->tx_dup_count = 0; + + // Reset RTT history + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + etcp->rtt_history_idx = 0; + etcp->last_rr_link = NULL; + + // Return unconfirmed inflight data to normalizer input (до нормалайзера) + 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); + clear_queue(etcp->output_queue); + clear_queue(etcp->input_send_q); + clear_queue(etcp->input_wait_ack); + clear_queue(etcp->recv_q); + clear_queue(etcp->ack_q); + + // clear_queue leaves callback_suspended=1, resume to prevent deadlock + queue_resume_callback(etcp->input_queue); + queue_resume_callback(etcp->input_send_q); + queue_resume_callback(etcp->input_wait_ack); + queue_resume_callback(etcp->output_queue); + queue_resume_callback(etcp->ack_q); + if (etcp->normalizer && etcp->normalizer->input) { + clear_queue(etcp->normalizer->input); + queue_resume_callback(etcp->normalizer->input); + } + +// В etcp_conn_reset(), после очистки очередей добавьте: + struct ETCP_LINK* l = etcp->links; + while (l) { + l->inflight_bytes = 0; + l->inflight_packets = 0; + l->delivered_bytes = 0; + l->acked_bytes = 0; + l->acked_packets = 0; + l->last_ack_time_tb = get_time_tb(); + l = l->next; + } + + // 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->reset_count++; + + // Reset normalizer (packer/unpacker state for reconnection) + if (etcp->normalizer) { + pn_reset(etcp->normalizer); + } + + queue_resume_callback(etcp->input_queue); + queue_resume_callback(etcp->output_queue); + queue_resume_callback(etcp->input_send_q); + queue_resume_callback(etcp->input_wait_ack); + queue_resume_callback(etcp->recv_q); + queue_resume_callback(etcp->ack_q); + + etcp->tx_state=ETCP_TX_STATE_DATA_WAIT; + + + int up=0; + struct ETCP_LINK* link = etcp->links; + while (link) { + if (link->link_status) up=1; + link = link->next; + } + + if (up) { + if (etcp->links_up==0) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "reset conn: set link up"); + etcp->links_up=1; + etcp_on_up(etcp); + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "reset conn: set link down/up"); + etcp_on_down(etcp, NULL); + etcp_on_up(etcp); + } + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "links not up - skip on_down/on_up"); + if (etcp->links_up) { + etcp->links_up=0; + etcp->setup_start_tb = get_time_tb(); + etcp_on_down(etcp, NULL); + } + } + + 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"); +} + +void etcp_links_reset(struct ETCP_CONN* etcp) {// Если сбой в обмене или ребутнулась одна из сторон -> необходимо заново переинициализировать соединение + // Сбрасываем initialized во всех линках + struct ETCP_LINK* link = etcp->links; + while (link) { + link->initialized = 0; + link = link->next; + } +} + +void etcp_conn_reinit(struct ETCP_CONN* etcp, const char* reason) {// Если сбой в обмене или ребутнулась одна из сторон -> необходимо заново переинициализировать соединение + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] REINIT: %s (init=%d links=%d reinit=%u tx=%d)", + etcp->log_name, reason, etcp->initialized, etcp->links_up, etcp->reinit_count, etcp->tx_state); + + etcp->setup_start_tb = get_time_tb(); + etcp->reinit_count++; + etcp->reinit_pending = 1; + etcp->reset_done = 0; + etcp->initialized = 0;// еще раз придёт conn_ready_callback + + // Отменяем висящие NAT-ping'и для этого соединения + if (etcp->instance && etcp->instance->nat_det) { + nat_detection_cancel_for_conn(etcp->instance->nat_det, etcp); + } + + // Сбрасываем ретрансмиты роутера ДО очистки ETCP-очередей (избегаем гонки с data_pool) + if (etcp->peer_node_id) etcp_router_pause_retrans_for_node(etcp->instance, etcp->peer_node_id); + + // Сбрасываем NAT-статус у всех линков этого соединения + struct ETCP_LINK* l = etcp->links; + while (l) { + l->nat_type = NAT_TYPE_UNKNOWN; + l->nat_check_status = NAT_CHECK_NONE; + l = l->next; + } + + // Вызываем etcp_conn_reset для сброса состояния + etcp_conn_reset(etcp); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "end"); +} + +// внутренняя функция. Вызывается один раз когда первый линк готов. +void etcp_conn_ready(struct ETCP_CONN* conn) { + if (!conn) return; + if (conn->initialized) return; // already ready + + conn->initialized = 1; + conn->reset_done = 1; + if (conn->tx_state == 0) { conn->tx_state = ETCP_TX_STATE_DATA_WAIT; } + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Connection ready", conn->log_name); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CRYPTO_CONN_READY: log=%s seskey=%02x%02x%02x%02x peer_pub=%02x%02x%02x%02x my_pub=%02x%02x%02x%02x links=%p", + conn->log_name, + conn->crypto_ctx.session_key[0], conn->crypto_ctx.session_key[1], + conn->crypto_ctx.session_key[2], conn->crypto_ctx.session_key[3], + conn->crypto_ctx.peer_public_key[0], conn->crypto_ctx.peer_public_key[1], + conn->crypto_ctx.peer_public_key[2], conn->crypto_ctx.peer_public_key[3], + conn->crypto_ctx.pk ? conn->crypto_ctx.pk->public_key[0] : 0, + conn->crypto_ctx.pk ? conn->crypto_ctx.pk->public_key[1] : 0, + conn->crypto_ctx.pk ? conn->crypto_ctx.pk->public_key[2] : 0, + conn->crypto_ctx.pk ? conn->crypto_ctx.pk->public_key[3] : 0, + (void*)conn->links); + + 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 + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] queue_set_ready: removing old entry=%p key=0 -> new key=0x%llx", + conn->log_name, (void*)conn->conn_queue_entry, (unsigned long long)conn->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_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] queue_set_ready: new entry=%p put in queue, count=%d", + conn->log_name, (void*)qe, queue_entry_count(conn->instance->connections)); + + 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); + + etcp_cbk_fire(conn, ETCP_CBK_EVENT_INIT); + if (conn->reinit_pending) { conn->reinit_pending = 0; etcp_cbk_fire(conn, ETCP_CBK_EVENT_REINIT); } + if (conn->links_up) etcp_on_up(conn); +} + + + + +// Update log_name when peer_node_id becomes known +void etcp_update_log_name(struct ETCP_CONN* etcp) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + if (!etcp || !etcp->instance) return; + uint16_t local_id = etcp->instance->node_id; + uint16_t peer_id = etcp->peer_node_id; + const char* name = etcp->name ? etcp->name : EMPTY_NAME; + if (etcp->instance->topo_groups && peer_id) { + struct TOPO_NODE* ni = topo_node_registry_find(etcp->instance->topo_groups, etcp->peer_node_id); + if (ni && ni->node_name && ni->node_name[0]) name = ni->node_name; + } + snprintf(etcp->log_name, sizeof(etcp->log_name), "%04X->%04X [%s]", local_id, peer_id, name); +} + + +// ====================================================================== Отправка данных в etcp после нормализации + +// Send data through ETCP connection +// Allocates memory from data_pool and places in input queue +// Returns: 0 on success, -1 on failure +int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + + if (!etcp || !data || len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] invalid parameters (etcp=%p, data=%p, len=%zu)", etcp->log_name, etcp, data, len); + return -1; + } + + if (!etcp->input_queue) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] input_queue is NULL for etcp=%p", etcp->log_name, etcp); + return -1; + } + + // Check length against maximum packet size + if (len > ETCP_MAX_PAYLOAD_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] packet too large (len=%zu, max=%d)", etcp->log_name, len, ETCP_MAX_PAYLOAD_SIZE); + return -1; + } + + // Allocate packet data from data_pool (following ETCP reception pattern) + uint8_t* packet_data = memory_pool_alloc(etcp->instance->data_pool); + if (!packet_data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate packet data from data_pool", etcp->log_name); + return -1; + } + + // Copy user data to packet buffer + memcpy(packet_data, data, len); + + struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(etcp->io_pool); + if (!pkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate queue entry", etcp->log_name); + memory_pool_free(etcp->instance->data_pool, packet_data); + return -1; + } + + pkt->seq = 0; // Will be assigned by input_queue_cb + pkt->timestamp = 0; // Will be set by input_queue_cb + pkt->ll.dgram = packet_data; // Point to data_pool allocation + pkt->ll.len = len; // размер packet_data + pkt->ll.dgram_pool = etcp->instance->data_pool; + pkt->ll.memlen = etcp->instance->data_pool->object_size; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] created PACKET %p with data %p (len=%zu)", etcp->log_name, pkt, packet_data, len); + + // Add to input queue - input_queue_cb will process it + if (queue_data_put(etcp->input_queue, (struct ll_entry*)pkt) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add to input queue", etcp->log_name); + queue_dgram_free(&pkt->ll); + queue_entry_free(&pkt->ll); + return -1; + } + + return 0; +} + + + + + +static void input_queue_try_push(struct ETCP_CONN* etcp) {// пробуем протолкнуть при отправке + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + + // когда очередь отправки пуста - пробуем взять новый пакет на обработку + size_t wait_ack_bytes = queue_total_bytes(etcp->input_wait_ack); + if (wait_ack_bytes <= etcp->optimal_inflight) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes); + if (etcp->input_queue->count > 0) input_queue_cb(etcp->input_queue, etcp); + else queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет + } +} + +static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + + // Сперва отправим всё из очереди отправки + size_t send_q_bytes = queue_total_bytes(etcp->input_send_q); +// queue_resume_callback(etcp->input_send_q);// вызвать лишний раз resume не страшно. + if (send_q_bytes>0) return; + + // когда очередь отправки пуста - пробуем взять новый пакет на обработку + size_t wait_ack_bytes = queue_total_bytes(etcp->input_wait_ack); + if (wait_ack_bytes <= etcp->optimal_inflight) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: inflight_bytes=%d, input_len=%d", etcp->log_name, wait_ack_bytes, etcp->input_queue->total_bytes); + queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет + } +} + +// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes. +// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up. +void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) { + if (!etcp) return; + uint32_t sum = 0; + for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes; + etcp->optimal_inflight = sum; +// input_queue_try_resume(etcp); +} + +void etcp_stats(struct ETCP_CONN* etcp) { + if (!etcp) return; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] stats for conn=%p:", etcp->log_name, etcp); + + // Queue statistics + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Queues:", etcp->log_name); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] input_queue: %zu pkts, %zu bytes", etcp->log_name, + queue_entry_count(etcp->input_queue), queue_total_bytes(etcp->input_queue)); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] input_send_q: %zu pkts, %zu bytes", etcp->log_name, + queue_entry_count(etcp->input_send_q), queue_total_bytes(etcp->input_send_q)); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] input_wait_ack: %zu pkts, %zu bytes", etcp->log_name, + queue_entry_count(etcp->input_wait_ack), queue_total_bytes(etcp->input_wait_ack)); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] ack_q: %zu pkts", etcp->log_name, + queue_entry_count(etcp->ack_q)); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] recv_q: %zu pkts", etcp->log_name, + queue_entry_count(etcp->recv_q)); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] output_queue: %zu pkts, %zu bytes", etcp->log_name, + queue_entry_count(etcp->output_queue), queue_total_bytes(etcp->output_queue)); + + // RTT metrics + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RTT metrics:", etcp->log_name); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] rtt_last: %u (0.1ms)", etcp->log_name, etcp->rtt_last); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] rtt_avg_10: %u (0.1ms)", etcp->log_name, etcp->rtt_avg_10); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] jitter: %u (0.1ms)", etcp->log_name, etcp->jitter); + + // Counters + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Counters:", etcp->log_name); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] bytes_sent_total: %u", etcp->log_name, etcp->bytes_sent_total); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] retransmissions_count: %u", etcp->log_name, etcp->retransmissions_count); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] ack_packets_count: %u", etcp->log_name, etcp->ack_packets_count); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unacked_bytes: %u", etcp->log_name, etcp->unacked_bytes); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] rx_dup_count: %u", etcp->log_name, etcp->rx_dup_count); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] tx_dup_count: %u", etcp->log_name, etcp->tx_dup_count); + + // IDs + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] IDs:", etcp->log_name); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] next_tx_id: %u", etcp->log_name, etcp->next_tx_id); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] last_rx_id: %u", etcp->log_name, etcp->last_rx_id); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] last_delivered_id:%u", etcp->log_name, etcp->last_delivered_id); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] rx_ack_till: %u", etcp->log_name, etcp->rx_ack_till); +} + +// Input callback for input_queue (добавление новых кодограмм в стек) +// input_queue -> input_send_q +static void input_queue_cb(struct ll_queue* q, void* arg) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + struct ETCP_FRAGMENT* in_pkt = (struct ETCP_FRAGMENT*)queue_data_get(q); + + if (!in_pkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] cannot get element (pool=%p etcp=%p)", etcp->log_name, etcp->inflight_pool, etcp); + queue_resume_callback(q); + return; + } + + + + // Create INFLIGHT_PACKET + + struct INFLIGHT_PACKET* p = (struct INFLIGHT_PACKET*)queue_entry_new_from_pool(etcp->inflight_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] cannot allocate INFLIGHT_PACKET (pool=%p etcp=%p)", etcp->log_name, etcp->inflight_pool, etcp); + queue_dgram_free(&in_pkt->ll); + queue_entry_free((struct ll_entry*)in_pkt); + queue_resume_callback(q); + return; + } + + // Setup inflight packet (based on protocol.txt) +// memset(p, 0, sizeof(*p)); + p->seq = etcp->next_tx_id++; // Assign seq + p->state = INFLIGHT_STATE_WAIT_SEND; + p->last_timestamp = 0; + p->ll.dgram = in_pkt->ll.dgram; + p->ll.dgram_pool = in_pkt->ll.dgram_pool; + p->ll.len = in_pkt->ll.len; + +// memory_pool_free(etcp->io_pool, in_pkt);// перемещаем из io_pool в inflight_pool + queue_entry_free(&in_pkt->ll); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] TX input -> inflight (seq=%u, len=%u); Qlen: in=%d snd=%d wait=%d", etcp->log_name, p->seq, p->ll.len, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count); + int len=p->ll.len;// сохраним len + + // Add to send queue + if (queue_data_put_with_index(etcp->input_send_q, &p->ll) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet seq=%u to input_send_q", etcp->log_name, p->seq); + queue_dgram_free(&p->ll); + queue_entry_free(&p->ll); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] EXIT (queue put failed)", etcp->log_name); + return; + } + etcp->unacked_bytes += len; + +// 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); +} + +static void ack_timeout_cb(void* arg); +static void ack_timeout_check(struct ETCP_CONN* etcp) { + uint64_t now = get_time_tb(); + int32_t timeout = (etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2) / 16; + if (timeout<50) timeout=50; + if (timeout>10000) timeout=10000; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "starting check, now=%llu, timeout=%llu, rtt_avg_10=%u, jitter=%u", + (unsigned long long)now, (unsigned long long)timeout, etcp->rtt_avg_10, etcp->jitter); + + + struct ll_entry* current; + current = etcp->input_wait_ack->head; + while (current) { + struct INFLIGHT_PACKET* pkt = (struct INFLIGHT_PACKET*)current; + + int64_t elapsed = now - pkt->last_timestamp; + if (elapsed > timeout) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] timeout for seq=%u, elapsed=%lld, now=%llu, timeout=%llu, send_count=%u. Moving: wait_ack -> send_q", + etcp->log_name, pkt->seq, (unsigned long long)elapsed, (unsigned long long)now, (unsigned long long)timeout, pkt->send_count); + + // Remove from wait_ack + pkt=(struct INFLIGHT_PACKET*)queue_data_get(etcp->input_wait_ack); + if (!pkt) break; + // Increment counters + pkt->retrans_req_count++; // Optional, if used for retrans request logic + pkt->last_timestamp = now; + + // Change state and add to send_q for retransmission + pkt->state = INFLIGHT_STATE_WAIT_SEND; + queue_data_put_with_index(etcp->input_send_q, (struct ll_entry*)pkt); + + // Update stats + etcp->retransmissions_count++; + etcp_metrics_add_loss(etcp, 1); + } + else {// не надо до конца сканировать - они уже сортированы по таймстемпу т.к. очередь fifo, а timestamp = время добавления в очередь = время отправки + // shedule timer + int64_t next_timeout=timeout - elapsed; + if (next_timeout<0) next_timeout=0; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans"); + return; + } + current = etcp->input_wait_ack->head; + } + queue_resume_callback(etcp->input_wait_ack); +} + +static void ack_timeout_cb(void* arg) {// сработал таймер переотправки + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + etcp->retrans_timer=NULL; + ack_timeout_check(etcp);// он установит новый таймер или выгребет всё и установит ожидание на очередь +} + +static void wait_ack_cb(struct ll_queue* q, void* arg) {// добавили пакет в ожидание ACK + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + if (!etcp->retrans_timer) ack_timeout_check(etcp);// если таймер ретрансмиссий взведен - дожидаемся таймера. +} + +static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_q data ready + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg; + etcp_conn_process_send_queue(etcp); + if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q"); + queue_resume_callback(etcp->input_send_q); + } else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy"); + } +/* +static void send_ack_req_cb(struct ll_queue* q, void* arg) {// etcp->ack_q data ready + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] processing", etcp->log_name); + + etcp_conn_process_send_queue(etcp); + if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) queue_resume_callback(etcp->ack_q); + } +*/ +void etcp_on_link_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + int up=0; + struct ETCP_LINK* link = etcp->links; + while (link) { + if (link->link_status) up=1; + link = link->next; + } + int was_up = etcp->links_up; + etcp->links_up = up; + if (up == 0 && was_up != 0) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "All links fall down"); + etcp_on_down(etcp, down_link); + } +} + +static void ack_response_timer_cb(void* arg) {// проверяем неотправленные ack response и отправляем если надо. + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + etcp->ack_resp_timer=NULL; + if (etcp->ack_q->count==0) return;// нечего отправлять + etcp_conn_process_send_queue(etcp);// проталкиваем (она же должна отправлять только ack если больше ничего нет) +// если ack все еще заняты - обновляем таймаут + if (etcp->ack_q->count) etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp"); +// else etcp->ack_resp_timer=NULL; +} + +static void etcp_link_ready_callback(struct ETCP_CONN* etcp) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + if (!etcp) return; + + if (etcp->tx_state==0) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "ETCP not ready, skip link state change"); + return;// not initialized + } + + if (etcp->links_up==0) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] etcp_link_ready_callback: links_up 0→1, calling etcp_on_up (initialized=%d tx_state=%d)", etcp->log_name, etcp->initialized, etcp->tx_state); + etcp->links_up=1; + etcp_on_up(etcp); + } else DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] etcp_link_ready_callback: links_up=%d already up", etcp->log_name, etcp->links_up); + + + if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return; + etcp->tx_state = ETCP_TX_STATE_DATA_WAIT; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait"); + queue_resume_callback(etcp->input_send_q); +// queue_resume_callback(etcp->ack_q); + if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) { + ack_response_timer_cb(etcp); + } +} + +// Process packets in send queue and transmit them +static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызываем когда есть элемент в send_q или надо отправить ack + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_DGRAM* dgram; + if (etcp->tx_state!=ETCP_TX_STATE_DATA_WAIT) { + 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 ", 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); + return; + } + dgram = etcp_request_pkt(etcp); + while(dgram) { + etcp_loadbalancer_send(dgram); + dgram = etcp_request_pkt(etcp); + } + if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) { + queue_resume_callback(etcp->input_send_q); + } +} + +// Подготовить и отправить кодограмму +// вызывается линком когда освобождается или очередью если появляются данные на передачу +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + struct ETCP_LINK* link = NULL; +// если есть активный burst — используем этот линк напрямую + for (struct ETCP_LINK* l = etcp->links; l; l = l->next) { + if (l->burst_active) { link = l; break; } + } + if (!link) link = etcp_loadbalancer_select_link(etcp); + if (!link) { + etcp->tx_state=ETCP_TX_STATE_LINK_WAIT; + etcp->cnt_link_wait++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no link available", etcp->log_name); + return NULL;// если линков нет - ждём появления свободного + } + etcp->tx_state=ETCP_TX_STATE_DATA_WAIT; + + size_t send_q_size = queue_entry_count(etcp->input_send_q); + + if (send_q_size == 0) {// сгребаем из input_queue +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_send_q empty, check if avail input_queue -> inflight"); + input_queue_try_push(etcp); + } + + + // First, check if there's a packet in input_send_q (retrans or new) +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "getting packet from input_send_q"); + struct INFLIGHT_PACKET* inf_pkt = (struct INFLIGHT_PACKET*)queue_data_get(etcp->input_send_q); + if (inf_pkt) { + uint64_t now=get_time_tb(); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] send_q->wait_ack seq=%d TS=%llu", etcp->log_name, inf_pkt->seq, now); + +// === NEW: per-link inflight + send_hist logic === + // Compute 1-based link number in the linked list (as requested) + uint8_t link_num = 0; + struct ETCP_LINK* tmp = etcp->links; + while (tmp) { + link_num++; + if (tmp == link) break; + tmp = tmp->next; + } + if (link_num == 0) link_num = 255; // safety (should never happen) + + // Fill history (send_count is the index before this attempt) + inf_pkt->send_hist[inf_pkt->send_count % 8] = link_num; + + // Always subtract from previous last_link (if any) – this handles retransmission + if (inf_pkt->last_link) { + inf_pkt->last_link->total_retransmissions++; + inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len; + bbr_note_loss(inf_pkt->last_link->bbr); + inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len; + inf_pkt->last_link->inflight_packets--; + } + + // Always add to the CURRENT link (first send or retransmission) + link->inflight_bytes += inf_pkt->ll.len; + link->inflight_packets++; + + // BBR: сохраняем снэпшот на момент отправки + inf_pkt->delivered_at_send = link->delivered_bytes; + inf_pkt->inflight_at_send = link->inflight_bytes; + inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0; + + // Update last_link for future ACK/retrans + inf_pkt->last_link = link; + + inf_pkt->last_timestamp=now; + inf_pkt->send_count++; + 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); + + if (!inf_pkt && ack_q_size == 0 && !link->burst_active) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no data/ack to send", etcp->log_name); + return NULL; + } + + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ETCP_DGRAM", etcp->log_name); + return NULL; + } + + dgram->link = link; + dgram->noencrypt_len=0; + dgram->timestamp = get_current_timestamp(); + +// формат ack: [01] [elements count] [4 байта last_delivered_id] [2 байта rx_dup_count] и <[4 байта seq][2 байта recv_ts][2 байта txrx delay ts]> x count + dgram->data[0]=1;// ack + int ptr=2; + + 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; + + int data_len=0; + if (inf_pkt) data_len=inf_pkt->ll.len; + int remain_len = link->mtu + - 28 /*udp headers*/ + - SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE + - (ETCP_ENCRYPTED_HDR_SIZE + ETCP_ACK_BASE_SIZE) + - (1 + sizeof(inf_pkt->seq)) /*payload hdr*/ + - data_len; +// DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "remain_len= %d pl=%d", remain_len, data_len); + +// добавим опциональные заголовки + struct ACK_PACKET* ack_pkt; + while (remain_len>=8) { + ack_pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q); + if (!ack_pkt) break; + remain_len-=8; + // seq 4 байта + dgram->data[ptr++]=ack_pkt->seq; + dgram->data[ptr++]=ack_pkt->seq>>8; + dgram->data[ptr++]=ack_pkt->seq>>16; + dgram->data[ptr++]=ack_pkt->seq>>24; + + // ts приема 2 байта + dgram->data[ptr++]=ack_pkt->recv_timestamp; + dgram->data[ptr++]=ack_pkt->recv_timestamp>>8; + + // время задержки 2 байта между recv и ack + uint16_t dly=get_current_timestamp()-ack_pkt->recv_timestamp; + dgram->data[ptr++]=dly; + dgram->data[ptr++]=dly>>8; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX Send ACK seq=%d need=%d", etcp->log_name, ack_pkt->seq, etcp->last_delivered_id+1); + queue_entry_free((struct ll_entry*)ack_pkt); + + if (inf_pkt && inf_pkt->ll.len+ptr>=etcp->mtu-10) break;// pkt len (надо просчитать точнее включая все заголовки) + if (ptr>500) break; + } + + dgram->data[1]=(ptr - 8) / 8; + + int max_enc = link->mtu - 28 - SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE - ETCP_ENCRYPTED_HDR_SIZE; + if (max_enc > PACKET_DATA_SIZE) max_enc = PACKET_DATA_SIZE; + if (max_enc < 0) max_enc = 0; + uint8_t burst_flags = 0; int pkt_sz_pos = 0; + +// === MEAS_RESP piggyback (если есть готовый ответ и не активен burst) === + if (link->burst_resp_pending && !link->burst_active && max_enc - ptr >= MEAS_RESP_SECTION_SIZE) { + dgram->data[ptr++] = ETCP_SECTION_MEAS_RESP; + dgram->data[ptr++] = link->burst_recv_id & 0xFF; dgram->data[ptr++] = (link->burst_recv_id >> 8) & 0xFF; + dgram->data[ptr++] = link->burst_resp_valid; + uint32_t v = link->burst_resp_gap_avg; + dgram->data[ptr++] = v & 0xFF; dgram->data[ptr++] = (v >> 8) & 0xFF; dgram->data[ptr++] = (v >> 16) & 0xFF; dgram->data[ptr++] = (v >> 24) & 0xFF; + v = link->burst_resp_gap_min; + dgram->data[ptr++] = v & 0xFF; dgram->data[ptr++] = (v >> 8) & 0xFF; dgram->data[ptr++] = (v >> 16) & 0xFF; dgram->data[ptr++] = (v >> 24) & 0xFF; + dgram->data[ptr++] = link->burst_resp_pkt_count; + link->burst_resp_pending = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX burst resp id=%u valid=%u gap_min=%u", etcp->log_name, link->burst_recv_id, link->burst_resp_valid, link->burst_resp_gap_min); + } + +// === MEAS_TS section (burst) === + if (link->burst_active && max_enc - ptr >= MEAS_TS_SECTION_SIZE) { + burst_flags = 0; + if (!inf_pkt) burst_flags |= MEAS_FLAG_IS_FILLER; + if (link->burst_seq == 0) burst_flags |= MEAS_FLAG_IS_FIRST; + if (link->burst_seq + 1 >= link->burst_count) burst_flags |= MEAS_FLAG_IS_LAST; + dgram->data[ptr++] = ETCP_SECTION_MEAS_TS; + dgram->data[ptr++] = link->burst_id & 0xFF; dgram->data[ptr++] = (link->burst_id >> 8) & 0xFF; + dgram->data[ptr++] = burst_flags; + dgram->data[ptr++] = link->burst_seq; + uint16_t ts_us = (uint16_t)get_time_us(); + dgram->data[ptr++] = ts_us & 0xFF; dgram->data[ptr++] = (ts_us >> 8) & 0xFF; + pkt_sz_pos = ptr; + dgram->data[ptr++] = 0; dgram->data[ptr++] = 0; + } + + + if (link->last_recv_updated && remain_len>=5) {// если есть данные - добавим channel_timestamp + uint64_t now=get_time_tb(); + uint64_t dt=now - link->last_recv_local_time; + link->last_recv_updated=0; + if (dt<1000000) { + dgram->data[ptr++]=ETCP_SECTION_TIMESTAMP; + + uint16_t t=link->last_recv_timestamp + dt; + dgram->data[ptr++]=t; + dgram->data[ptr++]=t>>8; + + t=link->last_recv_local_time - link->last_recv_timestamp; + dgram->data[ptr++]=t; + dgram->data[ptr++]=t>>8; + remain_len-=5; + } + } + +// DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "remain_len(2)= %d", remain_len); + + if (inf_pkt) { +// === FILLER section before PAYLOAD (burst padding) === + if (link->burst_active) { + int payload_overhead = 1 + 4 + data_len; // type + seq + data + int buf_avail = PACKET_DATA_SIZE - ptr - payload_overhead; + int filler_data = max_enc - ptr - FILLER_HDR_SIZE - payload_overhead; + if (filler_data > buf_avail) filler_data = buf_avail; + if (filler_data > 0) { + dgram->data[ptr++] = ETCP_SECTION_FILLER; + dgram->data[ptr++] = filler_data & 0xFF; + dgram->data[ptr++] = (filler_data >> 8) & 0xFF; + memset(&dgram->data[ptr], 0, filler_data); ptr += filler_data; + } + } + // фрейм 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); + + dgram->data[ptr++]=0;// payload + dgram->data[ptr++]=inf_pkt->seq; + dgram->data[ptr++]=inf_pkt->seq>>8; + dgram->data[ptr++]=inf_pkt->seq>>16; + dgram->data[ptr++]=inf_pkt->seq>>24; + + if ((int)(ptr + inf_pkt->ll.len) <= PACKET_DATA_SIZE) { + memcpy(&dgram->data[ptr], inf_pkt->ll.dgram, inf_pkt->ll.len); ptr+=inf_pkt->ll.len; + } else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload overflow: ptr=%d len=%u max=%d", etcp->log_name, ptr, inf_pkt->ll.len, PACKET_DATA_SIZE); + } + else { + if (link->burst_active) { + int buf_avail = PACKET_DATA_SIZE - ptr; + int filler_data = max_enc - ptr - FILLER_HDR_SIZE; + if (filler_data > buf_avail - FILLER_HDR_SIZE) filler_data = buf_avail - FILLER_HDR_SIZE; + if (filler_data > 0) { + dgram->data[ptr++] = ETCP_SECTION_FILLER; + dgram->data[ptr++] = filler_data & 0xFF; + dgram->data[ptr++] = (filler_data >> 8) & 0xFF; + memset(&dgram->data[ptr], 0, filler_data); ptr += filler_data; + } + } + int chk=queue_check_consistency(etcp->ack_q); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] only ACK (size=%d) packet with %d bytes total (chk=%d) rem=%d", etcp->log_name, ack_q_size, ptr, chk, remain_len); + } + if (ptr>=PACKET_DATA_SIZE-50) DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] SIZE ERROR!!! %d", etcp->log_name, ptr); + + dgram->data_len=ptr; + +// === Fill pkt_sz in MEAS_TS and advance burst state === + if (link->burst_active && pkt_sz_pos > 0) { + uint16_t pkt_sz = (uint16_t)dgram->data_len; + dgram->data[pkt_sz_pos] = pkt_sz & 0xFF; + dgram->data[pkt_sz_pos + 1] = (pkt_sz >> 8) & 0xFF; + link->burst_pkt_size = pkt_sz; + link->burst_seq++; + if (link->burst_seq >= link->burst_count) etcp_link_burst_finish(link); + } + + etcp_dump_pkt_sections(dgram, link, 1); + + return dgram; +} + +// ====================================================================== Прием данных + + +void etcp_output_try_assembly(struct ETCP_CONN* etcp) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + // пробуем собрать выходную очередь из фрагментов +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] etcp=%p, last_delivered_id=%u, recv_q_count=%d", +// etcp->log_name, etcp, etcp->last_delivered_id, queue_entry_count(etcp->recv_q)); + + uint32_t next_expected_id = etcp->last_delivered_id + 1; + int delivered_count = 0; + uint32_t delivered_bytes = 0; + + // Look for contiguous packets starting from next_expected_id + while (1) { + struct ETCP_FRAGMENT* rx_pkt = (struct ETCP_FRAGMENT*)queue_find_data_by_index(etcp->recv_q, &next_expected_id); + if (!rx_pkt) { + // No more contiguous packets found + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] no packet found for id=%u, stopping", etcp->log_name, next_expected_id); + break; + } + +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] assembling packet id=%u (len=%u)", etcp->log_name, +// rx_pkt->seq, rx_pkt->ll.len); + + // Simply move ETCP_FRAGMENT from recv_q to output_queue - no data copying needed + // Remove from recv_q first + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Remove from assembly queue"); + queue_remove_data(etcp->recv_q, (struct ll_entry*)rx_pkt); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move: ETCP -> PN"); + + // Add to output_queue using the same ETCP_FRAGMENT structure + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name, + next_expected_id, etcp->output_queue->count); + uint16_t pkt_len = rx_pkt->ll.len; + if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) { + 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, + next_expected_id); + // Put it back in recv_q if we can't add to output_queue + queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt); + break; + } + + // Update state for next iteration + etcp->last_delivered_id = next_expected_id; + next_expected_id++; + } + + if (delivered_count>0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] delivered %u contiguous packets (%u bytes), last_delivered_id=%u, asm_queue=%d, output_queue=%d", + etcp->log_name, delivered_count, delivered_bytes, etcp->last_delivered_id, queue_entry_count(etcp->recv_q), queue_entry_count(etcp->output_queue)); +} + +// Process ACK receipt - remove acknowledged packet from inflight queues +void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); + if (!etcp) return; + +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] processing ACK for seq=%u, ts=%u, dts=%u", etcp->log_name, seq, ts, dts); + + // Find the acknowledged packet in the wait_ack queue + struct INFLIGHT_PACKET* acked_pkt; + acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_wait_ack, &seq); + if (acked_pkt) { + etcp->cnt_ack_hit_inf++; + queue_remove_data(etcp->input_wait_ack, (struct ll_entry*)acked_pkt); + } + else { + acked_pkt = (struct INFLIGHT_PACKET*)queue_find_data_by_index(etcp->input_send_q, &seq); + if (acked_pkt) { + etcp->cnt_ack_hit_sndq++; + queue_remove_data(etcp->input_send_q, (struct ll_entry*)acked_pkt); + } + else etcp->cnt_ack_miss++; + } + + if (!acked_pkt) { + // Packet might be already acknowledged or not found + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "packet seq=%u not found in wait_ack queue", seq); + return; + } + + // === subtract inflight from the LAST link the packet was sent on === + if (acked_pkt->last_link) { + struct ETCP_LINK* link = acked_pkt->last_link; + link->inflight_bytes -= acked_pkt->ll.len; + link->inflight_packets--; + + // BBR: собираем rate_sample и вызываем bbr_main + uint64_t now_tb = get_time_tb(); + uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0; + struct bbr_rate_sample rs = { + .delivered = acked_pkt->ll.len, + .interval_us = interval_us, + .rtt_us = (uint32_t)link->rtt_last * 100, + .acked_sacked = acked_pkt->ll.len, + .prior_delivered = (uint32_t)acked_pkt->delivered_at_send, + .tx_in_flight = acked_pkt->inflight_at_send, + .lost = (int)link->bbr_loss_since_ack, + .is_app_limited = acked_pkt->is_app_limited, + }; + link->delivered_bytes += rs.delivered; + link->last_ack_time_tb = now_tb; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d", + link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us, + rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes, + rs.lost, rs.is_app_limited); + + uint32_t old_cwnd = link->inflight_lim_bytes; + uint32_t old_pacing = link->bbr_pacing_rate; + uint32_t new_cwnd = old_cwnd; + uint32_t new_pacing = old_pacing; + bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes, + (link->inflight_bytes >= link->inflight_lim_bytes)); + etcp_link_update_inflight_lim(link, new_cwnd); + link->bbr_pacing_rate = new_pacing; + link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000); + link->bbr_loss_since_ack = 0; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u", + link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing, + link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd); + + link->acked_bytes += acked_pkt->ll.len; + link->acked_packets++; + } + + + // Update connection statistics + if (etcp->unacked_bytes >= acked_pkt->ll.len) etcp->unacked_bytes -= acked_pkt->ll.len; + else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] unacked_bytes underflow prevented: %u < %u", etcp->log_name, etcp->unacked_bytes, acked_pkt->ll.len); + etcp->unacked_bytes = 0; + } + etcp->bytes_sent_total += acked_pkt->ll.len; + etcp->ack_packets_count++; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX removed packet seq=%u from wait_ack, unacked_bytes now %u total acked=%u", etcp->log_name, seq, etcp->unacked_bytes, etcp->ack_packets_count); + + if (acked_pkt->ll.dgram) { + memory_pool_free(etcp->instance->data_pool, acked_pkt->ll.dgram); + } + memory_pool_free(etcp->inflight_pool, acked_pkt); + + // Try to resume sending more packets if window space opened up + input_queue_try_resume(etcp); + +// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] completed for seq=%u", etcp->log_name, seq); +} + + +// Process incoming decrypted packet +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, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt); + volatile int _halt = 1; while (_halt) {} + } + if (!pkt->data_len) { + memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt); + return; + } + + etcp_dump_pkt_sections(pkt, pkt->link, 0); + + pkt->link->last_recv_local_time = get_time_tb(); + + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; // Received timestamp + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len); + + while (len >= 1) { + uint8_t type = data[0]; + + // Process sections as per protocol.txt + switch (type) { + case ETCP_SECTION_ACK: { + if (len < 2) { len = 0; break; } + int elm_cnt=data[1]; + uint32_t till=data[2] | (data[3]<<8) | (data[4]<<16) | (data[5]<<24); + uint16_t rx_dup_count_16 = data[6] | (data[7]<<8); + uint16_t old_tx_dup_16 = etcp->tx_dup_count & 0xFFFF; + int16_t diff = (int16_t)(rx_dup_count_16 - old_tx_dup_16); + etcp->tx_dup_count += diff; + int ack_section_len = 8 + elm_cnt * 8; + if (ack_section_len > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] ACK section too large: elm_cnt=%d len=%d", etcp->log_name, elm_cnt, len); + len = 0; + break; + } + data+=ack_section_len; + len-=ack_section_len; + for (int i=0; irx_ack_till-till)<0) { etcp->rx_ack_till++; etcp_ack_recv(etcp, etcp->rx_ack_till, -1, -1); }// подтверждаем всё по till + break; + } + case ETCP_SECTION_TIMESTAMP: { + if (len < 5) { len = 0; break; } + uint16_t cur_ts=get_current_timestamp(); + 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 этого пакета (на стороне отправителя, т.е. у нас) + + int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts; + int recv_dt_tx=recv_dt_tx1 - pkt->link->rtt_last/2 - 1000; + if (pkt->link->recv_dt_avg_rx==0) pkt->link->recv_dt_avg_rx=recv_dt_rx*65536; + if (pkt->link->recv_dt_avg_tx==0) pkt->link->recv_dt_avg_tx=recv_dt_tx*65536; + pkt->link->recv_dt_avg_rx +=((int32_t)(recv_dt_rx*65536 - pkt->link->recv_dt_avg_rx))/32; + pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32; + pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536; + pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536; + //tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету) + data+=5; len-=5; + + int prev_rtt = pkt->link->rtt_last; + int d_rtt = new_rtt - prev_rtt; + if (d_rtt<0) d_rtt=-d_rtt; + pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32; + + struct ETCP_LINK* c=etcp->links; + int rtt_sum=0; + int rtt_max=0; + int tt_sum=0; + int j_sum=0; + int cnt=0; + while (c) { + rtt_sum += c->rtt_last; + if (rtt_max < c->rtt_last) rtt_max = c->rtt_last; + tt_sum += c->tt_last; + j_sum += c->jitter/32768; + cnt++; + c=c->next; + } + etcp->rtt_last=rtt_sum/cnt; + etcp->rtt_avg_10=rtt_max; + etcp->tt_last=tt_sum/cnt; + etcp->jitter=j_sum/cnt; + if (etcp->rtt_last) { + uint64_t now_tb = get_time_tb(); + if (now_tb - etcp->last_rtt_cb_time > RTT_CB_PERIOD_TB) { + topo_node_ping_update_rtt(etcp->instance->topo_groups, etcp->peer_node_id, etcp->rtt_last); + etcp->last_rtt_cb_time = now_tb; + } + } + break; + } + case ETCP_SECTION_PAYLOAD: { + + if (len>=5) { + // формируем 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; DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Initial packet seq=1 received", etcp->log_name); } + else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Out-of-order pkt before init seq=%d, waiting seq=1", + etcp->log_name, seq); + len=0; + break; + } + } + else { + int32_t d=seq - etcp->last_delivered_id; + if (d>MAX_INFLIGHT_SIZE || d<-MAX_INFLIGHT_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] Received packet out of inflight bounds: seq=%d last delivered=%d", etcp->log_name, seq, etcp->last_delivered_id); + len=0; + break; + } + } + if (queue_find_data_by_index(etcp->ack_q, &seq) == NULL) { + struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name); + len = 0; + break; + } + p->seq=seq; + p->pkt_timestamp=pkt->timestamp; + p->recv_timestamp=get_current_timestamp(); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq); + + queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p); + if (etcp->ack_resp_timer == NULL) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name); + etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp"); + } + } + if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq + uint32_t pkt_len=len-5; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id); + // отправляем пакет в очередь на сборку + uint8_t* payload_data = memory_pool_alloc(etcp->instance->data_pool); + if (!payload_data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate payload_data from data_pool", etcp->log_name); + len=0; + break; + } + struct ETCP_FRAGMENT* rx_pkt = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(etcp->io_pool); + if (!rx_pkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate rx_pkt from io_pool", etcp->log_name); + memory_pool_free(etcp->instance->data_pool, payload_data); + len=0; + break; + } + rx_pkt->seq = seq; + rx_pkt->timestamp = pkt->timestamp; + rx_pkt->ll.dgram = payload_data; + rx_pkt->ll.len = pkt_len; + rx_pkt->ll.dgram_pool = etcp->instance->data_pool; + rx_pkt->ll.memlen = etcp->instance->data_pool->object_size; + if (pkt_len > (int)etcp->instance->data_pool->object_size) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload too large for data_pool: %u > %zu", etcp->log_name, pkt_len, etcp->instance->data_pool->object_size); + memory_pool_free(etcp->instance->data_pool, payload_data); + queue_entry_free(&rx_pkt->ll); + len = 0; + break; + } + if (data + len > pkt->data + pkt->data_len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload section bounds overflow: data+len=%p > end=%p", etcp->log_name, (void*)(data + len), (void*)(pkt->data + pkt->data_len)); + memory_pool_free(etcp->instance->data_pool, payload_data); + queue_entry_free(&rx_pkt->ll); + len = 0; + break; + } + // Copy the actual payload data + 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++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX dup: seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count); + } + } else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "payload len %d < 5", len); + len=0; + break; + } + + case ETCP_SECTION_MEAS_TS: { + if (len < MEAS_TS_SECTION_SIZE) { len = 0; break; } + uint16_t b_id = data[1] | (data[2] << 8); + uint8_t flags = data[3]; + uint8_t seq = data[4]; + uint16_t pkt_sz = data[7] | (data[8] << 8); + struct ETCP_LINK* link = pkt->link; + if (b_id != link->burst_recv_id) { + link->burst_recv_id = b_id; + link->burst_recv_count = BURST_PACKET_COUNT; + link->burst_recv_next_seq = 0; + link->burst_recv_valid = 1; + link->burst_recv_received = 0; + } + if (link->burst_recv_valid) { + 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; + link->burst_recv_received++; + link->burst_recv_pkt_size = pkt_sz; + } + } + if ((flags & MEAS_FLAG_IS_LAST) || link->burst_recv_received >= link->burst_recv_count) { + uint8_t total = (flags & MEAS_FLAG_IS_LAST) ? link->burst_recv_received : link->burst_recv_count; + if (link->burst_recv_valid && total >= BURST_SKIP_COUNT + 2) { + uint32_t sum_gap = 0, min_gap = UINT32_MAX; + uint8_t measured = 0; + for (uint8_t i = BURST_SKIP_COUNT; i + 1 < total; i++) { + uint64_t dt = link->burst_recv_times[i + 1] - link->burst_recv_times[i]; + uint32_t gap = (uint32_t)(dt > 0 ? dt : 1); + sum_gap += gap; measured++; + if (gap < min_gap) min_gap = gap; + } + if (measured > 0) { + link->burst_resp_gap_avg = sum_gap / measured; + link->burst_resp_gap_min = min_gap; + link->burst_resp_pkt_count = measured; + link->burst_resp_valid = 1; + } + } + link->burst_resp_pending = 1; + } + data += MEAS_TS_SECTION_SIZE; len -= MEAS_TS_SECTION_SIZE; + break; + } + + case ETCP_SECTION_MEAS_RESP: { + if (len < MEAS_RESP_SECTION_SIZE) { len = 0; break; } + uint16_t b_id = data[1] | (data[2] << 8); + uint8_t valid = data[3]; + uint32_t gap_avg = data[4] | (data[5] << 8) | ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24); + uint32_t gap_min = data[8] | (data[9] << 8) | ((uint32_t)data[10] << 16) | ((uint32_t)data[11] << 24); + uint8_t pkt_cnt = data[12]; + struct ETCP_LINK* link = pkt->link; + if (b_id == link->burst_id - 1 || b_id == link->burst_id) { + if (link->burst_resp_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->burst_resp_timer); link->burst_resp_timer = NULL; } + if (valid == MEAS_RESP_VALID && gap_min > 0 && link->burst_pkt_size > 0) { + uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min; + if (bw_kbps > 10000000) bw_kbps = 10000000; + link->bandwidth = (uint32_t)bw_kbps; + float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.f; + if (rtt_sec < 0.005f) rtt_sec = 0.005f; + link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec); + // BBR: инициализируем верхнюю границу из burst + if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp; + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u", + link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt); + } + } + data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE; + break; + } + + case ETCP_SECTION_FILLER: { + if (len < FILLER_HDR_SIZE) { len = 0; break; } + uint16_t fill_len = data[1] | (data[2] << 8); + if ((uint16_t)(fill_len + FILLER_HDR_SIZE) > len) { len = 0; break; } + data += FILLER_HDR_SIZE + fill_len; len -= FILLER_HDR_SIZE + fill_len; + 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, "unknown section type=0x%02x", type); + len=0; + break; + } + + } + + if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt); + volatile int _halt = 1; while (_halt) {} + } + + + memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram +} + +void etcp_update_mtu(struct ETCP_CONN* etcp) { + if (!etcp) return; + int new_mtu = PACKET_DATA_MAX_MTU; + int has_links = 0; + struct ETCP_LINK* link = etcp->links; + while (link) { + has_links = 1; + if (link->mtu > 0 && link->mtu < new_mtu) new_mtu = link->mtu; + link = link->next; + } + if (!has_links) new_mtu = ETCP_RFC791_MIN_MTU; + if (new_mtu != etcp->mtu) { + etcp->mtu = new_mtu; + if (etcp->normalizer) etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE; + } +} + +// ====================================================================== Метрики: гистограммы 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); + } +} + diff --git a/src/transport_layer/etcp.h b/src/transport_layer/etcp.h deleted file mode 100644 index 3b5d074e..00000000 --- a/src/transport_layer/etcp.h +++ /dev/null @@ -1,340 +0,0 @@ -// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt) -#ifndef ETCP_H -#define ETCP_H - -#include "etcp_connections.h" -#include "secure_channel.h" -#include "../lib/ll_queue.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#include "pkt_normalizer.h" - -struct stcp_link; // forward declaration -// In struct ETCP_CONN, add: -//struct pn_pair* normalizer; - -// Forward declarations -struct UTUN_INSTANCE; -struct ETCP_CONN; -struct etcp_cbk_entry; // defined in etcp_api.h -struct UASYNC; - -uint16_t get_current_timestamp(void); - -// ETCP packet section types (from protocol spec) -#define ETCP_SECTION_PAYLOAD 0x00 // Data payload -#define ETCP_SECTION_ACK 0x01 // ACK section -#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed) -#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (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 -#define BURST_SKIP_COUNT 3 // сколько первых пакетов пропустить при замере gap -#define MIN_BURST_INTERVAL_TB 5000 // минимальный интервал между burst (500ms в 0.1ms) -#define BURST_RESP_TIMEOUT_TB 20000 // таймаут ожидания ответа на burst (2s в 0.1ms) - -// Inflight phases -#define INFLIGHT_PHASE_SLOW_START 0 -#define INFLIGHT_PHASE_CONG_AVOIDANCE 1 - -// Section sizes -#define MEAS_TS_SECTION_SIZE 9 // type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2) -#define MEAS_RESP_SECTION_SIZE 13 // type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1) -#define FILLER_HDR_SIZE 3 // type(1) + len(2) - -// MEAS_TS flags -#define MEAS_FLAG_IS_FILLER 0x01 // пакет содержит FILLER вместо PAYLOAD -#define MEAS_FLAG_IS_LAST 0x02 // последний пакет в burst -#define MEAS_FLAG_IS_FIRST 0x04 // первый пакет в burst - -// MEAS_RESP valid -#define MEAS_RESP_VALID 1 -// Метрики: гистограммы 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 - -#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак) -#define ASM_BUF_MAX_SIZE (64 * 1024) // максимальный размер буфера сборки фрагментов - -// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool. -// пакет полностью удаляется когда приходит ACK (либо conn_reset/close) -struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool - struct ll_entry ll; - uint32_t seq; // packet seq (ID по документации) - struct ETCP_LINK* last_link; // Last sent link - uint64_t last_timestamp; // Last send timestamp - uint8_t send_count; // Number of sends - uint8_t retrans_req_count; // Number of retrans requests - uint8_t state; // WAIT_ACK or WAIT_SEND - uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr - uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered) - uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight) - uint8_t is_app_limited; // данные ограничены приложением на момент отправки -}; - -// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем) -struct ETCP_FRAGMENT {// выделяется из пула etcp->rx_pool - struct ll_entry ll; - uint32_t seq; - uint16_t timestamp; -}; - -struct ACK_PACKET { - struct ll_entry ll; - uint32_t seq;// sequence number - uint16_t pkt_timestamp;// timestamp пакета (часы уладенной стороны) - uint32_t recv_timestamp;// время приема (локальное) -}; - -#define ETCP_TX_STATE_DATA_WAIT 1 -#define ETCP_TX_STATE_LINK_WAIT 2 - -// ETCP connection structure (refactored) -struct ETCP_CONN { - // State: 0=not ready, 1=ready (indexed in instance->connections), 2=deleted - int state; // 0=pending, 1=ready, 2=deleted (phase 1 of close done) - int ref_count; // External reference count. >0 blocks deferred resource free. - // Take/free via etcp_conn_ref_take()/etcp_conn_ref_free(). - - int mtu; - - struct UTUN_INSTANCE* instance; - - // Queue entries in instance->connections or instance->pending_connections - struct ll_entry* conn_queue_entry; // entry в очереди instance - struct ll_queue* conn_queue; // указатель на очередь где лежим (pending или connections) - - // Links (channels) - linked list - struct ETCP_LINK* links; - struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin - uint8_t tcp_link_count; // количество UP TCP линков - - // Crypto and state - struct secure_channel crypto_ctx; - struct PKTNORM* normalizer; - - // 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) - - // Inflight очереди (2 шт) - пока пакет в статусе inflight - к нему прикрепляется struct INFLIGHT_PACKET - struct memory_pool* inflight_pool; // память для inflight очередей - struct memory_pool* io_pool; // память для rx очередей - - struct ll_queue* input_send_q; // очередь на отправку (inflight_pool -> INFLIGHT_PACKET) - struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (inflight_pool -> struct INFLIGHT_PACKET) - struct ll_queue* ack_q; // неотправленные подтверждения приема пакетов (instance.ack_pool -> struct ACK_PACKET) + index [+0 SEQ] - - struct ll_queue* recv_q; // очередь на сборку фрагментированных пакетов(rx_pool -> struct ETCP_FRAGMENT) + index [+0 SEQ] - - 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; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет) - uint32_t last_rx_id; // Last received ID - uint32_t last_delivered_id; // Last delivered to output_queue - - uint32_t rx_ack_till;// из ack пакета - по какой пакет получено и собрано на удаленной стороне - - // Metrics (RTT, jitter, etc.) - // uint16_t retrans_delay; // Not used - uint16_t rtt_last;// round-trip time - uint16_t tt_last;// transmission time (время пакета до меня) -// uint16_t rxt_last;// recv relative time (бустро регаирует на изменение задержки rx и медленно на изменение задержки tx) - uint16_t rtt_avg_10; - uint16_t jitter; - uint64_t last_rtt_cb_time; // время последнего вызова ping_update_rtt (0.1ms) - uint32_t bytes_sent_total; - // uint32_t bytes_received_total; // Not used - uint32_t retransmissions_count; - uint32_t reinit_count; - uint64_t setup_start_tb; // время начала/рестарта подключения (0.1ms) - uint32_t reset_count; -// uint32_t bytes_sent_norx; // сколько отправили байт без единого ответного пакета (для детекции запроса реконнекта) - - // Window and inflight management - uint32_t unacked_bytes; // Current inflight bytes - uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd) - uint32_t optimal_inflight; // Sum over links - - // Timers - void* retrans_timer; // Retrans check timer - void* ack_resp_timer; // ACK send timer - - // Bandwidth measurement state - // uint8_t burst_in_progress; // Burst transmission flag - Not used - // uint16_t burst_start_id; // Start ID for burst - Not used - - // Statistics counters - uint32_t ack_packets_count; // Count of ACK packets received - // uint16_t last_rx_ack_id; // Last ACK ID received - Not used - uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10) - uint8_t rtt_history_idx; // Current index in RTT history - // uint32_t total_packets_sent; // Total packets sent counter - Not used - - // Flags - 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) - uint8_t reset_done; // 0 - рукопожатие не завершено (реинит разрешён), 1 - соединение стабильно (реинит заблокирован) - uint8_t callbacks_running; // 1 - внутри итерации колбэк-цепочек, etcp_connection_close запрещён - - // Unified callback chain with event mask (init/reinit/up/down/node_changed) - struct etcp_cbk_entry* cbks; - uint8_t reinit_pending; // 1 = reinit в процессе, ждём завершения - void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен) - - uint8_t fin_wait : 1; // close-pending: ожидаем подтверждение закрытия от remote - void (*fin_wait_clear_cb)(struct ETCP_CONN* conn, void* arg); // вызывается при сбросе fin_wait - void* fin_wait_clear_arg; - - uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight - uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq - uint32_t cnt_ack_miss; // счетчик не найденных ack - uint32_t cnt_link_wait; // счетчик переходов в ожидание когда link busy - uint32_t rx_dup_count; // счетчик принятых дубликатов (локальные) - uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK) - uint32_t debug[8]; // 8 значений для дебага (live watch) - - 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 -}; - -#define RTT_CB_PERIOD_TB 600000 // 1 минута в 0.1ms - -// Functions -struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* name); -void etcp_connection_close(struct ETCP_CONN* etcp); -void etcp_conn_queue_set_ready(struct ETCP_CONN* conn); // move pending->connections, fire ready cbks - -/** - * @brief Take a reference on the connection (blocks deferred resource free). - * @param conn connection - * @return 0 on success, -1 if conn is NULL or already in state 2 (deleted). - * - * Increments ref_count. While ref_count > 0, etcp_connection_close() - * will detach but defer resource cleanup until all references are released. - * Always pair with etcp_conn_ref_free(). - */ -int etcp_conn_ref_take(struct ETCP_CONN* conn); - -/** - * @brief Release a reference. Triggers deferred resource free if last ref and state==2. - * @param conn connection - * - * Decrements ref_count. If ref_count reaches 0 and the connection has been closed - * (state == 2), schedules deferred resource cleanup via uasync_call_soon. - * Safe to call on NULL. - */ -void etcp_conn_ref_free(struct ETCP_CONN* conn); - -void etcp_conn_reset(struct ETCP_CONN* etcp); -void etcp_links_reset(struct ETCP_CONN* etcp); - -void etcp_conn_reinit(struct ETCP_CONN* etcp, const char* reason); - -void etcp_connection_ready(struct ETCP_CONN* etcp);// вызывается когда подключение инициализировано - -// Отправка: используем api ll_queue для очереди ETCP_CONN.input_queue -// Прием: используем api ll_queue для очереди ETCP_CONN.output_queue -// для очередей используется формат - -// Input from etcp_connections (decrypted packet) -void etcp_conn_input(struct ETCP_DGRAM* pkt); - -// Send data through ETCP connection -// Allocates memory from data_pool and places in input queue -// Returns: 0 on success, -1 on failure -int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len); - -// Request next packet for load balancer -struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); - -// Process ACK receipt - remove acknowledged packet from inflight queues -void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts); - -// Recalculate optimal_inflight and resume input queue after link inflight_lim change -void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp); - -// Fire connection status callback on instance's conn_status_cbks chain -void etcp_fire_conn_status(struct ETCP_CONN* conn, int status); - -// Update log_name when peer_node_id becomes known -void etcp_update_log_name(struct ETCP_CONN* etcp); - -// Вызывается стеком etcp когда соединение установлено (можно передавать данные) -void etcp_conn_ready(struct ETCP_CONN* conn); - -void etcp_on_link_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link); - -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 - -#endif // ETCP_H \ No newline at end of file diff --git a/src/transport_layer/etcp_connect.c b/src/transport_layer/etcp_connect.c deleted file mode 100644 index e8cefb18..00000000 --- a/src/transport_layer/etcp_connect.c +++ /dev/null @@ -1,393 +0,0 @@ -#include "etcp_api.h" -#include "etcp.h" -#include "etcp_connect.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 - -#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; - uint16_t min_rtt; - uint8_t early_delivered : 1; - uint8_t done : 1; -}; -static void connect_init_cb(struct ETCP_CONN* conn, int event, 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, int free_nodes) { - 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)) { - node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type); - if (free_nodes) { *pp = node->next; u_free(node); } else { pp = &node->next; } - } 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_GROUP_NODE* node) { - if (!node) return; - struct TOPO_NODE* ni = topo_node_registry_find(ctx->instance->topo_groups, node->node_id); - if (!ni) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] links_v4: ni NOT in registry for node=%016llx", (unsigned long long)node->node_id); return; } - int addr_count = 0, sock_count = 0, link_count = 0; - { struct ETCP_SOCKET* s = ctx->instance->etcp_sockets; while (s) { sock_count++; s = s->next; } } - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - addr_count++; - 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)) link_count++; s = s->next; } - } - if ((a->protocol & TOPO_PROTO_TCP)) { - struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, NULL, NULL, 0); - if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; } - } - } - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] links_v4: addrs=%d socks=%d links=%d for node=%016llx", - addr_count, sock_count, link_count, (unsigned long long)node->node_id); -} - -static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct TOPO_GROUP_NODE* node) { - if (!node) return; - struct TOPO_NODE* ni = topo_node_registry_find(ctx->instance->topo_groups, node->node_id); - if (!ni) return; - for (const struct TOPO_ADDR6* a = ni->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); - int is_ll = (a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80); - if (!(a->protocol & TOPO_PROTO_TCP)) { - struct ETCP_SOCKET* s = ctx->instance->etcp_sockets; - while (s) { if (s->local_addr.ss_family == AF_INET6) { - if (is_ll) sin6.sin6_scope_id = s->netif_index; - struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); - etcp_link_new(ctx->conn, s, &sa, 0); - } s = s->next; } - } - if (a->protocol & TOPO_PROTO_TCP) { - if (is_ll) { struct ETCP_SOCKET* sv = ctx->instance->etcp_sockets; - while (sv) { if (sv->local_addr.ss_family == AF_INET6 && sv->netif_index) - { sin6.sin6_scope_id = sv->netif_index; break; } sv = sv->next; } - } - struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); - struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, NULL, NULL, 0); - if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); } - } - } -} - -static void connect_bgp_ready_cb(struct ETCP_CONN* conn) { - struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id); - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] bgp_ready_cb: node=%016llx ctx=%p ctx_done=%d ra=%d", - (unsigned long long)conn->peer_node_id, (void*)ctx, ctx ? ctx->done : -1, conn->routing_exchange_active); - if (!ctx || ctx->done) return; - connect_deliver(ctx, ETCP_CONNECT_BGP_READY, 1); -} - -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; - connect_deliver(ctx, 0, 1); - 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; - } - etcp_conn_remove_cbk(ctx->conn, connect_init_cb, ctx); - connect_deliver(ctx, ETCP_CONNECT_LATE, 1); - connect_cancel(ctx); -} - -static void connect_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event; - struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg; - if (!ctx || ctx->done) return; - - int total_links = 0, up_links = 0; - struct ETCP_LINK* link = conn->links; - while (link) { - total_links++; - if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt) - ctx->min_rtt = link->rtt_last; - if (link->link_state == 3) up_links++; - link = link->next; - } - - if (total_links == 1 || up_links == total_links || (total_links == 0 && conn->tcp_link_count > 0)) { - if (!ctx->early_delivered) { - ctx->early_delivered = 1; - if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; } - } - DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] immediate LATE for node 0x%016llx (links=%d up=%d)", - (unsigned long long)ctx->node_id, total_links, up_links); - { struct etcp_connect_cb_node* n = ctx->cb_list; while (n) { struct etcp_connect_cb_node* next = n->next; if (n->flags & ETCP_CONNECT_EARLY) n->cb(n->arg, ctx->conn, ETCP_CONNECT_EARLY); if (n->flags & ETCP_CONNECT_LATE) n->cb(n->arg, ctx->conn, ETCP_CONNECT_LATE); u_free(n); n = next; } ctx->cb_list = NULL; } - connect_cancel(ctx); - return; - } - - 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 (links=%d up=%d)", - (unsigned long long)ctx->node_id, ctx->min_rtt, total_links, up_links); - connect_deliver(ctx, ETCP_CONNECT_EARLY, 0); - 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_GROUP_NODE* node, - etcp_connect_callback_t cb, void* arg, uint8_t flags) { - if (!inst || !node || !cb) return -1; - - struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node->node_id); - if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node not in registry"); return -1; } - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] registry OK node=%016llx pub=%016llx v4addrs=%p v4socks=%p", - (unsigned long long)node->node_id, *(uint64_t*)ni->public_key, (void*)ni->v4_addrs, (void*)ni->v4_sock_meta); - - uint64_t node_id = ni->node_id; - if (node_id == 0) { - if (!ni->public_key) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node_id=0 and public_key is NULL"); - return -1; - } - int zero = 1; - for (int i = 0; i < SC_PUBKEY_SIZE; i++) - if (ni->public_key[i]) { zero = 0; break; } - if (zero) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node_id=0 and public_key is all zeros"); - return -1; - } - node_id = sc_derive_node_id_from_pubkey(ni->public_key); - ni->node_id = node_id; - } - - struct ETCP_CONN* conn = instance_find_conn(inst, node_id); - struct ETCP_CONNECT* ctx = connect_find(inst, node_id); - - // Case 1: conn && ctx — already connecting - if (conn && ctx) { - if (ctx->done) { - DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx: existing ctx timed out, cb(NULL,0)", - (unsigned long long)node_id); - cb(arg, NULL, 0); - return 0; - } - 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; - } - - // Case 2: !conn && ctx — context exists but conn was removed (rare) - if (!conn && 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; - return 0; - } - - // Case 3: conn && !ctx — existing connection, no context - if (conn && !ctx) { - if (conn->state == 0) { - // Pending conn from another path (e.g. incoming INIT before etcp_connect) - ctx = u_calloc(1, sizeof(struct ETCP_CONNECT)); - if (!ctx) 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); return -1; } - cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn; - etcp_conn_add_cbk(conn, connect_init_cb, ctx, ETCP_CBK_EVENT_INIT); - conn->bgp_ready_cbk = connect_bgp_ready_cb; - 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] node 0x%016llx: existing pending conn, waiting for init", - (unsigned long long)node_id); - return 0; - } - - // conn->state == 1: already ready — deliver EARLY+LATE immediately (via direct loop to deliver both phases) - ctx = u_calloc(1, sizeof(struct ETCP_CONNECT)); - if (!ctx) return -1; - ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn; - ctx->early_delivered = 1; - { - struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node)); - if (!cn) { u_free(ctx); return -1; } - cn->cb = cb; cn->arg = arg; cn->flags = flags; - ctx->cb_list = cn; - } - - DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx: existing ready conn, delivering EARLY+LATE", - (unsigned long long)node_id); - struct etcp_connect_cb_node* n = ctx->cb_list; - struct etcp_connect_cb_node* keep = NULL; - while (n) { - struct etcp_connect_cb_node* next = n->next; - if (n->flags & ETCP_CONNECT_EARLY) n->cb(n->arg, ctx->conn, ETCP_CONNECT_EARLY); - if (n->flags & ETCP_CONNECT_LATE) n->cb(n->arg, ctx->conn, ETCP_CONNECT_LATE); - if (n->flags & ETCP_CONNECT_BGP_READY) { - if (conn->routing_exchange_active >= 3) n->cb(n->arg, ctx->conn, ETCP_CONNECT_BGP_READY); - else { n->next = keep; keep = n; n = next; continue; } - } - u_free(n); - n = next; - } - ctx->cb_list = keep; - if (keep) { - conn->bgp_ready_cbk = connect_bgp_ready_cb; - ctx->next = inst->pending_connects; inst->pending_connects = ctx; - } else { - u_free(ctx); - } - return 0; - } - - // Case 4: !conn && !ctx — new connection - 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->peer_node_id = node_id; - etcp_update_log_name(conn); - 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) { struct conn_queue_entry* ce = (struct conn_queue_entry*)qe->data; ce->peer_node_id = node_id; ce->conn = conn; conn->conn_queue_entry = qe; conn->conn_queue = inst->connections; queue_data_put_with_index(inst->connections, qe); } } - - 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, ni->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_cbk(conn, connect_init_cb, ctx, ETCP_CBK_EVENT_INIT); - conn->bgp_ready_cbk = connect_bgp_ready_cb; - - connect_create_links_v4(ctx, node); - connect_create_links_v6(ctx, node); - - if (!conn->links && conn->tcp_link_count == 0) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx (links=%p tcp_links=%d)", - (unsigned long long)node_id, (void*)conn->links, conn->tcp_link_count); - connect_deliver(ctx, 0, 1); - 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; -} - -void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn) { - if (!inst || !conn) return; - struct ETCP_CONNECT** pp = &inst->pending_connects; - while (*pp) { - struct ETCP_CONNECT* ctx = *pp; - if (ctx->conn == conn) { - *pp = ctx->next; - ctx->done = 1; - ctx->conn = NULL; - 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); - } else { - pp = &(*pp)->next; - } - } -} diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 38b1b893..64a913cd 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -52,6 +52,8 @@ static void tcp_server_on_link(struct stcp_link *link, void *arg) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: accepted TCP from peer=0x%016llx", (unsigned long long)node_id); struct ETCP_CONN *conn = instance_find_conn(inst, node_id); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: %s conn=%p peer=0x%016llx pubkey=%016llx", + conn ? "existing" : "NEW", (void*)conn, (unsigned long long)node_id, *(const uint64_t*)pubkey); if (!conn) { conn = etcp_connection_create(inst, NULL); if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: etcp_connection_create failed"); return; } @@ -1065,11 +1067,15 @@ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link) { link->recv_keepalive = 1; if (!link->mtu_remote) link->mtu_remote = link->mtu; etcp->initialized = 1; etcp->links_up = 1; etcp->tcp_link_count++; + if (etcp->tx_state == 0) etcp->tx_state = ETCP_TX_STATE_DATA_WAIT; etcp_link_send_keepalive(link); start_keepalive_timer(link); loadbalancer_link_ready(link); - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d tcp_links=%d)", etcp->log_name, link->local_link_id, link->mtu, etcp->tcp_link_count); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d init=%d up=%d tcp_links=%d)", + etcp->log_name, link->local_link_id, link->mtu, + etcp->initialized, etcp->links_up, etcp->tcp_link_count); etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_UP); + etcp_cbk_fire(etcp, ETCP_CBK_EVENT_INIT); } static void tcp_link_reconnect_cb(void *arg) { @@ -1094,6 +1100,8 @@ void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage if (!sl) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_tcp_link_start_connect: stcp_link_connect failed"); return; } link->tcp_link = sl; stcp_link_set_etcp_conn(sl, link->etcp); stcp_link_set_etcp_link(sl, link); if (link->is_server == 0) stcp_link_set_on_close(sl, tcp_link_close_cb, link); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d → stcp_link_connect %s:%u rc=%p", + link->etcp->log_name, link->local_link_id, addr, port, (void*)sl); } void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link) { @@ -1117,9 +1125,14 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) { struct ETCP_LINK *link = dgram->link; link->pkt_sent_since_keepalive = 1; dgram->flag_up = link->recv_keepalive; - if (link->send_hook) return (int)link->send_hook(0, dgram->data, dgram->data_len, NULL, 0, link, link->send_hook_ctx); - if (link->tcp_link) return stcp_link_send(link->tcp_link, dgram->data, dgram->data_len) == 0 ? (int)dgram->data_len : -1; - return -1; + int rc; + if (link->send_hook) rc = (int)link->send_hook(0, dgram->data, dgram->data_len, NULL, 0, link, link->send_hook_ctx); + else if (link->tcp_link) rc = stcp_link_send(link->tcp_link, dgram->data, dgram->data_len) == 0 ? (int)dgram->data_len : -1; + else rc = -1; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TCP send: link=%d dlen=%d rc=%d via=%s", + link->etcp->log_name, link->local_link_id, dgram->data_len, rc, + link->send_hook ? "hook" : (link->tcp_link ? "stcp" : "NONE")); + return rc; } diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h deleted file mode 100644 index bfb379fe..00000000 --- a/src/transport_layer/etcp_connections.h +++ /dev/null @@ -1,388 +0,0 @@ -#ifndef ETCP_CONNECTIONS_H -#define ETCP_CONNECTIONS_H - -#ifdef __cplusplus -extern "C" { -#endif - - -// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover) - -#include "secure_channel.h" -#include "utun_instance.h" -#include "etcp_bbr.h" -#include "../lib/socket_compat.h" -#include "../lib/ll_queue.h" -#include -#include - -#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт] -#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr -#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки -#define SC_ENCRYPT_OVERHEAD (3 + SC_TAG_SIZE) // timestamp(2)+flag_up(1)+AES-CCM tag(16) — поверх plaintext -#define IP_UDP_OVERHEAD_V4 (20 + 8) // IP(20)+UDP(8) -#define IP_UDP_OVERHEAD_V6 (40 + 8) // IP(40)+UDP(8) -#define WIRE_OVERHEAD(fam) ((fam) == AF_INET6 ? IP_UDP_OVERHEAD_V6 + SC_ENCRYPT_OVERHEAD : IP_UDP_OVERHEAD_V4 + SC_ENCRYPT_OVERHEAD) - -#define INFLIGHT_LIM_MIN 8192 // 8K -#define INFLIGHT_LIM_MAX 1048576 // 1M - -#define PACKET_DATA_SIZE 1600//1536 -#define PACKET_DATA_MAX_MTU 1600 -#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */ -#define ETCP_RFC791_MIN_MTU 576 - -// Типы кодограмм протокола -#define ETCP_INIT_REQUEST 0x02 -#define ETCP_INIT_RESPONSE 0x03 -#define ETCP_INIT_REQUEST_NOINIT 0x04 -#define ETCP_INIT_RESPONSE_NOINIT 0x05 -#define ETCP_PING 0x06 -#define ETCP_PONG 0x07 -#define ETCP_KEEPALIVE 0x08 - -#define ETCP_PING_FLAG_WANT_RTT 0x01 // в PONG: хочу получить RTT от пингера -#define ETCP_PING_FLAG_SEND_RTT 0x02 // в PING: несу RTT для получателя - -/* Адаптивный keepalive */ -#define KA_PERIOD_MIN_MS 200 -#define KA_PERIOD_MAX_MS 10000 -#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */ - - -#pragma pack(push, 1) -struct ETCP_DGRAM {// пакет (незашифрованный) - struct ETCP_LINK* link;// откуда получена или куда отправялем - uint16_t data_len;// общий размер пакета не включая timestamp - uint16_t noencrypt_len;// число байт (с конца) которые не надо шифровать. для передачи pubkey - uint16_t timestamp;// timestamp отправляющего узла при отправке пакета - uint8_t flag_up:1;// bit0 = up/down (recv_keepalive) - uint8_t data[];// данные пакета (без timestamp) -}; -#pragma pack(pop) - -/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */ -#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp)) - -/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */ -#define ETCP_ACK_BASE_SIZE (2 + 4 + 2) - -// --- 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) - uint8_t collision; // 29: 1 = cross-connect, remote claims master - uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 30: 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 { - struct PING_CONTEXT* next; - struct UTUN_INSTANCE* instance; - etcp_ping_callback_t cb; - void* arg; - uint64_t nonce; - void* timeout_timer; - uint64_t send_time; // время отправки пинга в 0.1ms - uint8_t peer_pubkey[SC_PUBKEY_SIZE]; - uint8_t* user_data; - size_t user_data_len; -}; - -#define LINK_ADDR_KEY_SIZE 19 // port(2) + addr(16) + family(1) - -struct link_queue_entry { - uint8_t key[LINK_ADDR_KEY_SIZE]; - struct ETCP_LINK* link; -}; - -// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений -struct ETCP_SOCKET { - struct ETCP_SOCKET* next; // Linked list для всех соединений - struct UTUN_INSTANCE* instance; - char name[MAX_CONN_NAME_LEN]; // Socket name from config (e.g., "lan1" from [server: lan1]) - socket_t fd; // UDP socket (cross-platform) - struct sockaddr_storage local_addr; // Локальный адрес - int mtu; // MTU для этого сокета - - int errorcode; - size_t pkt_format_errors; - - void* socket_id; // Socket ID from uasync_add_socket - uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE - uint8_t sock_id; // unique socket id (0-255) for NAT matching - uint8_t nat_type; // NAT_TYPE_* (detected by server) - uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order) - uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers - uint8_t only_local; // 1 = only local connections, no forwarding - uint32_t netif_index; // индекс интерфейса для sin6_scope_id (0 = не задан) - struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига - struct sockaddr_storage nat_addr; // NAT адрес (network byte order), ss_family=0 = не определён - struct ll_queue* links_queue; // хеш-очередь линков, ключ = link_queue_entry.key (19 байт) -}; - -// NAT check status -#define NAT_CHECK_NONE 0 -#define NAT_CHECK_WAITING 1 -#define NAT_CHECK_IN_PROGRESS 2 -#define NAT_CHECK_EIM 3 -#define NAT_CHECK_STRICT 4 - -// NAT type (detected by server during NAT check) -#define NAT_TYPE_UNKNOWN 0 -#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping -#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric -#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 -#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive -#define NAT_VERIFIED_EIM 5 // EIM NAT -#define NAT_VERIFIED_STRICT 6 // strict NAT -#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT - - -// Тип функции-перехватчика отправки UDP. -// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto. -struct ETCP_LINK; -typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len, - const struct sockaddr* addr, socklen_t addr_len, - struct ETCP_LINK* link, void* context); - - -// ETCP Link - одно динамическое соединение (один путь) -struct ETCP_LINK { - struct ll_entry* link_queue_entry; // элемент в socket->links_queue - struct ETCP_LINK* next; // Linked list подключений для ETCP_CONN (каждое подключение это child для ETCP_CONN) - - struct ETCP_CONN* etcp; // подключение (parent) - struct ETCP_SOCKET* conn; // сокет через который работаем - // Путь соединения - struct sockaddr_storage remote_addr; // Удалённый адрес - - // Параметры соединения - uint16_t mtu; // MTU общий - uint16_t mtu_local; // MTU моего зла - uint16_t mtu_remote; // MTU удаленного узла - uint16_t keepalive_interval; // Keepalive интервал (x1ms) - uint32_t recovery_interval; // recovery интервал (x0.1ms) - uint8_t is_server; // инициирует подключение клиент - uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request) - uint8_t local_link_id; // id моего линка - uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized) - uint8_t remote_socket_id; // socket id peer - uint8_t remote_only_local; // only_local flag from peer - uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket) - uint8_t nat_type; // NAT_TYPE_* (detected for this client link) - uint8_t 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) - uint8_t link_state; // 0 - just init, 1 - handshake, 2 - try reconnect, 3 - connected - - // Состояние установки соединения (только для клиентов) - void* init_timer; // Таймер для повторов INIT (NULL=не подключается) - uint16_t init_timeout; // Текущий таймаут в мс - uint16_t init_retry_count; // Счетчик попыток - -// uint64_t last_activity; // Время последней активности - uint64_t last_recv_local_time; // x0.1 ms - uint16_t last_recv_timestamp; - uint8_t last_recv_updated; // =1 при обновлении timestamp, =0 при отправке (чтобы не дублировать отправки при отсутствии обновлений) - - uint64_t shaper_load_time_tb; // основной в 0.1 мс units - uint64_t shaper_sub_nanotime; // sub 0-999999 для 0.1 нс - 0.1 мс - uint8_t shaper_state; - void* shaper_timer; - - uint32_t inflight_bytes; - uint32_t inflight_packets; - uint32_t inflight_lim_bytes; - - // statistics - size_t encrypt_errors; - size_t decrypt_errors; - size_t send_errors; - size_t recv_errors; - size_t total_encrypted; - size_t total_decrypted; - uint32_t total_retransmissions; - uint64_t acked_bytes; // подтверждённых байт на этом линке - uint64_t acked_packets; // подтверждённых пакетов на этом линке - - uint16_t rtt_last; // round trip (время отправки + приёма) - uint32_t jitter; // Current jitter [>>16] x0.1 ms - // rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100) - uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256 - uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256 - - uint16_t tt_last; // transmit time (время доставки отправленных пакетов) - uint16_t rt_last; // recv time (время доставки принятых пакетов) - uint32_t bandwidth; // Link bandwidth in Kbits/sec - - // NAT address tracking (from INIT_RESPONSE) - uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set - uint16_t nat_port; // NAT port (host byte order) - uint32_t nat_changes_count; // Counter of NAT address changes - uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port) - uint8_t nat_check_status; // NAT_CHECK_* - - // Keepalive state - void* keepalive_timer; // Таймер для отправки keepalive пакетов - uint32_t keepalive_timeout; // таймаут (ms) - uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200) - uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика - uint32_t keepalive_sent_count; // Счётчик отправленных keepalive - uint32_t keepalive_recv_count; // Счётчик полученных keepalive - uint16_t handshake_minsize; // минимальный размер udp при handshake - uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом) - - // === Burst-измерение bandwidth === - // Sender-сторона - uint16_t burst_id; // монотонно возрастающий ID burst - uint8_t burst_active; // 1 = burst в процессе отправки - uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1) - uint8_t burst_count; // общее число пакетов в burst - uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL - void* burst_resp_timer; // таймер ожидания ответа на burst - uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты) - uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW - - // Receiver-сторона - uint16_t burst_recv_id; // ID отслеживаемого burst - uint8_t burst_recv_count; // ожидаемое число пакетов в burst - uint8_t burst_recv_next_seq; // следующий ожидаемый seq - uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен - uint8_t burst_recv_received; // сколько пакетов уже принято - uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs) - uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа) - uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback - uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs) - uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs) - uint8_t burst_resp_pkt_count; // число пакетов в измерении - uint8_t burst_resp_valid; // валидность измерения - - // === BBR congestion control === - struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new) - uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection) - uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us) - uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper - uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost) - - etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую - void* send_hook_ctx; - - uint8_t is_tcp; // 1 = TCP транспорт (STCP) - struct stcp_link *tcp_link; // STCP линк (для TCP) - void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент) - uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff) -}; - -// Единая точка отправки всех ETCP-пакетов. -ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len, - const struct sockaddr* addr, socklen_t addr_len); - -// INITIALIZATION -// Создаёт только listen-сокеты из конфига (серверы для incoming connections) -int init_sockets(struct UTUN_INSTANCE* instance); -// Создаёт listen-сокеты и client connections из конфига -int init_connections(struct UTUN_INSTANCE* instance); - -// SOCKET FUNCTIONS -// добавляет новый версер (сокет для приёма и отправки кодограмм. обслуживает много подключений) -struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server); -// удаляет сокет и освобождает ресурсы (грохает все его подключения и сокет) -void etcp_socket_remove(struct ETCP_SOCKET* conn); - -// TCP socket descriptor (analogous to ETCP_SOCKET for UDP) -struct TCP_SOCKET { - struct TCP_SOCKET* next; - struct UTUN_INSTANCE* instance; - char name[MAX_CONN_NAME_LEN]; - uint8_t sock_id; - uint8_t type; // CFG_SERVER_TYPE_* - struct sockaddr_storage local_addr; - struct sockaddr_storage interface_addr; - struct sockaddr_storage nat_addr; -}; - -struct TCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server); -void tcp_socket_remove(struct TCP_SOCKET* sock); - -// connection functions -// создает новый канал связи для etcp подключения (ETCP_CONN) -struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server); -void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim); -void etcp_link_close(struct ETCP_LINK* link); -//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет -int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет -// find link by address -struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr); - -// find free local_link_id for connection -// scans all links in connection, marks used ids in bit array -// returns first free id (0-255) or -1 if all occupied -int etcp_find_free_local_link_id(struct ETCP_CONN* etcp); - -// Burst measurement functions -void etcp_link_burst_start(struct ETCP_LINK* link); -void etcp_link_burst_check(struct ETCP_LINK* link); -void etcp_link_burst_finish(struct ETCP_LINK* link); - -void etcp_link_enter_ready_tcp(struct ETCP_LINK *link); -void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage *addr, uint16_t port); -void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link); - -int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin, - const struct sockaddr_storage* addr, int timeout_ms, - etcp_ping_callback_t cb, void* user_arg, - const uint8_t* user_data, size_t user_data_len); - -// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback. -// flags: ETCP_PING_FLAG_SEND_RTT — user_data содержит RTT для peer -int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock, - const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr, - int timeout_ms, etcp_ping_callback_t cb, void* user_arg, - const uint8_t* user_data, size_t user_data_len, - uint8_t flags); - -void etcp_connections_read_callback_socket(socket_t sock, void* arg); - - -#ifdef __cplusplus -} -#endif -#endif // ETCP_CONNECTIONS_H diff --git a/src/transport_layer/stcp_link.c b/src/transport_layer/stcp_link.c index 119fa97b..41c8a255 100644 --- a/src/transport_layer/stcp_link.c +++ b/src/transport_layer/stcp_link.c @@ -50,16 +50,22 @@ static void link_rx_cb(struct ll_queue *q, void *arg) { if (!e) { queue_resume_callback(q); return; } if (link->etcp_conn && link->etcp_link && link->cfg.inst) { - struct ETCP_DGRAM *pkt = memory_pool_alloc(link->cfg.inst->pkt_pool); - if (pkt) { - pkt->link = link->etcp_link; - pkt->data_len = (uint16_t)e->len; - pkt->noencrypt_len = 0; - if (e->len > 0) memcpy(pkt->data, e->dgram, e->len); - etcp_conn_input(pkt); + if (e->len >= 1 && e->dgram[0] == ETCP_KEEPALIVE) { + struct ETCP_LINK *l = link->etcp_link; + if (e->len >= 3) { uint16_t pp = e->dgram[1] | ((uint16_t)e->dgram[2] << 8); l->keepalive_timeout = (uint32_t)pp * KA_TIMEOUT_MULT; } + l->recv_keepalive = 1; l->remote_keepalive = 1; l->link_status = 1; l->keepalive_recv_count++; + l->last_recv_local_time = get_time_tb(); + queue_dgram_free(e); queue_entry_free(e); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_link rx → etcp_conn_input len=%zu link=%p", e->len, (void*)link->etcp_link); + struct ETCP_DGRAM *pkt = memory_pool_alloc(link->cfg.inst->pkt_pool); + if (pkt) { + pkt->link = link->etcp_link; pkt->data_len = (uint16_t)e->len; pkt->noencrypt_len = 0; + if (e->len > 0) memcpy(pkt->data, e->dgram, e->len); + etcp_conn_input(pkt); + } + queue_dgram_free(e); queue_entry_free(e); } - queue_dgram_free(e); - queue_entry_free(e); } else { struct UTUN_INSTANCE *inst = link->cfg.inst; uint8_t id = (e->dgram && e->len > 0) ? e->dgram[0] : 0; @@ -110,9 +116,9 @@ static void client_ready_cb(struct stcp_conn *conn, void *arg) { queue_set_waiter_defer(link->tx_queue, 1); stcp_conn_set_tx_queue(conn, link->tx_queue); - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: client connected, peer_pubkey=%02x%02x%02x%02x...", - link->cfg.peer_pubkey[0], link->cfg.peer_pubkey[1], - link->cfg.peer_pubkey[2], link->cfg.peer_pubkey[3]); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: client handshake OK, link=%p etcp_link=%p", + (void*)link, (void*)link->etcp_link); + if (link->etcp_link) etcp_link_enter_ready_tcp(link->etcp_link); if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg); } diff --git a/src/transport_layer/stcp_link.h b/src/transport_layer/stcp_link.h deleted file mode 100644 index cfa3764b..00000000 --- a/src/transport_layer/stcp_link.h +++ /dev/null @@ -1,68 +0,0 @@ -// stcp_link.h — STCP link management (TCP connection via STCP protocol) -#ifndef STCP_LINK_H -#define STCP_LINK_H - -#ifdef __cplusplus -extern "C" { -#endif - - -#include "secure_channel.h" -#include "../lib/u_async.h" -#include "../lib/socket_compat.h" -#include - -struct stcp_link; // opaque -struct stcp_server; // opaque -struct ETCP_CONN; -struct ETCP_LINK; -struct UTUN_INSTANCE; - -// ====== Link config ====== - -struct stcp_link_config { - struct UASYNC *ua; - struct SC_MYKEYS *my_keys; // ключи этой стороны - struct UTUN_INSTANCE *inst; // для диспатча через api_bindings - const uint8_t *peer_pubkey; // pubkey пира (клиент) - int peer_pubkey_mode; // 0=binary, 1=hex - const struct sockaddr_storage *remote_addr; // адрес пира (клиент) - uint16_t remote_port; // порт пира (клиент) - int listen_family; // AF_INET или AF_INET6 для сервера (0 = v4) -}; - -// ====== TCP server ====== - -typedef void (*stcp_server_on_link_cb)(struct stcp_link *link, void *arg); - -struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port, - stcp_server_on_link_cb on_link, void *arg); -void stcp_link_server_destroy(struct stcp_server *srv); - -/* Server list management (multiple servers per instance) */ -void stcp_server_list_add(struct UTUN_INSTANCE *inst, struct stcp_server *srv); -void stcp_server_list_destroy_all(struct UTUN_INSTANCE *inst); -int stcp_server_list_count(struct UTUN_INSTANCE *inst); - -// ====== TCP client link ====== - -struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg); -void stcp_link_close(struct stcp_link *link); -int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len); -int stcp_link_is_ready(struct stcp_link *link); - -struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link); -void stcp_link_set_etcp_conn(struct stcp_link *link, struct ETCP_CONN *conn); -void stcp_link_set_etcp_link(struct stcp_link *link, struct ETCP_LINK *elink); -const struct sockaddr_storage *stcp_link_get_remote_addr(struct stcp_link *link); - -typedef void (*stcp_link_cb)(struct stcp_link *link, void *arg); -void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg); -void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg); -const uint8_t *stcp_link_get_peer_pubkey(struct stcp_link *link); - - -#ifdef __cplusplus -} -#endif -#endif diff --git a/tests/test_etcp_stcp.c b/tests/test_etcp_stcp.c new file mode 100644 index 00000000..cafb0286 --- /dev/null +++ b/tests/test_etcp_stcp.c @@ -0,0 +1,153 @@ +// test_etcp_stcp.c — integration: etcp_send + etcp_bind over STCP link +#include "stcp_link.h" +#include "etcp_api.h" +#include "etcp.h" +#include "etcp_connections.h" +#include "secure_channel.h" +#include "topo_group.h" +#include "topo_node.h" +#include "../src/utun_instance.h" +#include "../lib/u_async.h" +#include "../lib/ll_queue.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include +#include +#include +#include + +#define TEST_PORT 25678 + +static int test_failed = 0; +static struct SC_MYKEYS s_keys, c_keys; +static struct UTUN_INSTANCE *srv_inst, *cli_inst; +static struct ETCP_CONN *srv_conn, *cli_conn; +static int g_srv_recv_count = 0; +static uint8_t g_srv_recv_data[256]; + +#define TASSERT(cond) do { \ + if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \ +} while(0) + +static void etcp_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) { + (void)conn; + g_srv_recv_count++; + if (entry->dgram && entry->len) { + size_t n = entry->len < 256 ? entry->len : 255; + memcpy(g_srv_recv_data, entry->dgram, n); + } + queue_dgram_free(entry); + queue_entry_free(entry); +} + +static void connect_cb(void *arg, struct ETCP_CONN *conn, int type) { + (void)arg; + if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_connect failed"); test_failed = 1; return; } + if (type & ETCP_CONNECT_EARLY) { + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "etcp_connect: EARLY ready"); + cli_conn = conn; + } +} + +static char *build_server_config(int port) { + static char buf[512]; + snprintf(buf, sizeof(buf), + "[global]\n" + "my_node_id=0xAAAAAAAAAAAAAAAA\n" + "my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n" + "my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n" + "\n" + "[server: stcp]\n" + "addr=127.0.0.1:%d\n" + "type=public\n" + "transport=tcp\n", + port); + return buf; +} + +static char *build_client_config(int port) { + static char buf[512]; + snprintf(buf, sizeof(buf), + "[global]\n" + "my_node_id=0xBBBBBBBBBBBBBBBB\n" + "my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" + "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" + "\n" + "[server: stcp]\n" + "addr=127.0.0.1:%d\n" + "type=public\n" + "transport=tcp\n", + port); + return buf; +} + +int main(void) { + debug_config_init(); + debug_set_level(DEBUG_LEVEL_INFO); + debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== etcp_send/bind over STCP link ==="); + + srand((unsigned)time(NULL)); + struct UASYNC *ua = uasync_create(); TASSERT(ua); + utun_instance_set_tun_init_enabled(0); + + int port = TEST_PORT + rand() % 1000; + + srv_inst = utun_instance_create_from_str(ua, build_server_config(port)); + TASSERT(srv_inst); + TASSERT(utun_instance_init(srv_inst) >= 0); + + cli_inst = utun_instance_create_from_str(ua, build_client_config(port + 1)); + TASSERT(cli_inst); + TASSERT(utun_instance_init(cli_inst) >= 0); + + TASSERT(etcp_bind(srv_inst, 0, etcp_recv_cb) >= 0); + + struct TOPO_GROUP_NODE *srv_node = topo_groups_get_default(srv_inst->topo_groups) ? topo_groups_get_default(srv_inst->topo_groups)->local_node : NULL; + TASSERT(srv_node); + { struct TOPO_NODE* srv_ni = topo_node_registry_find(srv_inst->topo_groups, srv_node->node_id); TASSERT(srv_ni); + struct TOPO_NODE* cli_ni = u_calloc(1, sizeof(struct TOPO_NODE)); + TASSERT(cli_ni); memcpy(cli_ni, srv_ni, sizeof(struct TOPO_NODE)); cli_ni->group_ref_count = 0; + cli_ni->v4_sock_meta = NULL; cli_ni->v4_addrs = NULL; cli_ni->v6_sock_meta = NULL; cli_ni->v6_addrs = NULL; + struct TOPO_ADDR4* a = memory_pool_alloc(cli_inst->topo_groups->v4_addr_pool); + TASSERT(a); a->addr[0]=127; a->addr[1]=0; a->addr[2]=0; a->addr[3]=1; a->port = port; + a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP; + a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a; + topo_node_registry_store(cli_inst->topo_groups, cli_ni); } + cli_inst->etcp_connect_timeout_tb = 100000; + TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0); + + int ticks = 0; + while (!cli_conn && ticks < 5000) { uasync_poll(ua, 10); ticks++; } + TASSERT(cli_conn); + + { struct ll_entry* e = srv_inst->connections->head; + while (e) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; + struct ETCP_CONN *c = ce->conn; + struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp) { srv_conn = c; break; } l = l->next; } + if (srv_conn) break; + e = e->next; } + } + TASSERT(srv_conn); + + const char *msg = "hello via etcp_send!"; + struct ll_entry *e = queue_entry_new(0); + e->dgram = u_malloc(strlen(msg) + 1); + strcpy((char *)e->dgram, msg); + e->len = (uint16_t)strlen(msg); + TASSERT(etcp_send(cli_conn, e) == 0); + + ticks = 0; + while (g_srv_recv_count < 1 && ticks < 2000) { uasync_poll(ua, 10); ticks++; } + TASSERT(g_srv_recv_count == 1); + TASSERT(memcmp(g_srv_recv_data, msg, strlen(msg)) == 0); + + srv_inst->running = 0; cli_inst->running = 0; + utun_instance_destroy(srv_inst); srv_inst = NULL; + utun_instance_destroy(cli_inst); cli_inst = NULL; + uasync_destroy(ua, 0); + + if (test_failed) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED ==="); return 1; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== PASSED ==="); + return 0; +} diff --git a/tests/test_stcp_traffic.c b/tests/test_stcp_traffic.c index 86ff8ff6..922aeaad 100644 --- a/tests/test_stcp_traffic.c +++ b/tests/test_stcp_traffic.c @@ -2,8 +2,8 @@ #include "stcp_link.h" #include "etcp_api.h" #include "etcp.h" -#include "secure_channel.h" #include "etcp_connections.h" +#include "secure_channel.h" #include "topo_group.h" #include "topo_node.h" #include "../src/utun_instance.h" @@ -27,8 +27,6 @@ static int test_failed = 0; if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return 1; } \ } while(0) -// ====== receive state ====== - struct rx_ctx { uint8_t *buf; size_t len, cap; @@ -37,15 +35,11 @@ struct rx_ctx { size_t expected_total; }; -// ====== globals ====== - static struct rx_ctx srv_rx, cli_rx; static struct UTUN_INSTANCE *srv_inst, *cli_inst; -static struct stcp_link *srv_link, *cli_link; +static struct ETCP_CONN *srv_conn, *cli_conn; static int srv_send_ready, cli_send_ready; -// ====== receive callbacks ====== - static void srv_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) { (void)conn; if (!entry->dgram || entry->len < 5) { queue_dgram_free(entry); queue_entry_free(entry); return; } @@ -74,21 +68,16 @@ static void cli_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) { if (cli_rx.len >= cli_rx.expected_total) cli_rx.done = 1; } -// ====== etcp_connect callback ====== - static void connect_cb(void *arg, struct ETCP_CONN *conn, int type) { (void)arg; if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_connect failed"); test_failed = 1; return; } if (type & ETCP_CONNECT_EARLY) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "etcp_connect: EARLY ready"); - struct ETCP_LINK *l = conn->links; while (l) { if (l->is_tcp && l->tcp_link) { cli_link = l->tcp_link; break; } l = l->next; } - if (!cli_link) { test_failed = 1; return; } + cli_conn = conn; cli_send_ready = 1; } } -// ====== packet helpers ====== - static size_t gen_packet(uint8_t *buf, int side, int seq, size_t paylen) { buf[0] = (uint8_t)side; buf[1] = (uint8_t)(seq >> 0); @@ -100,14 +89,18 @@ static size_t gen_packet(uint8_t *buf, int side, int seq, size_t paylen) { return paylen + 5; } -static int send_packet(struct stcp_link *link, int side, int seq, size_t paylen) { +static int send_etcp_packet(struct ETCP_CONN *conn, int side, int seq, size_t paylen) { uint8_t buf[PKT_MAX + 5]; size_t tot = gen_packet(buf, side, seq, paylen); - return stcp_link_send(link, buf, tot); + struct ll_entry *e = queue_entry_new(0); + if (!e) return -1; + e->dgram = u_malloc(tot); + if (!e->dgram) { queue_entry_free(e); return -1; } + memcpy(e->dgram, buf, tot); + e->len = (uint16_t)tot; + return etcp_send(conn, e); } -// ====== config strings ====== - static char *build_server_config(int port) { static char buf[512]; snprintf(buf, sizeof(buf), @@ -140,12 +133,10 @@ static char *build_client_config(int port) { return buf; } -// ====== main ====== - int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO); + debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO | DEBUG_CATEGORY_ETCP); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== STCP Traffic Test ==="); srand((unsigned)time(NULL)); @@ -178,23 +169,22 @@ int main(void) { a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP; a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a; topo_node_registry_store(cli_inst->topo_groups, cli_ni); } - // Increase timeout for TCP STCP handshake - cli_inst->etcp_connect_timeout_tb = 100000; // 10 seconds + cli_inst->etcp_connect_timeout_tb = 100000; TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0); int ticks = 0; while (!cli_send_ready && ticks < 5000) { uasync_poll(ua, 10); ticks++; } TASSERT(cli_send_ready); - TASSERT(cli_link); + TASSERT(cli_conn); { struct ll_entry* e = srv_inst->connections->head; while (e) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; struct ETCP_CONN *c = ce->conn; - { struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp && l->tcp_link) { srv_link = l->tcp_link; break; } l = l->next; } } - if (srv_link) break; + struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp) { srv_conn = c; break; } l = l->next; } + if (srv_conn) break; e = e->next; } } - TASSERT(srv_link); + TASSERT(srv_conn); size_t remaining_s = TRAFFIC_MB, remaining_c = TRAFFIC_MB; int seq_s = 0, seq_c = 0; @@ -207,14 +197,14 @@ int main(void) { if (!srv_done && remaining_s > 0) { size_t sz = PKT_MIN + (rand() % (PKT_MAX - PKT_MIN + 1)); if (sz > remaining_s) sz = remaining_s; - if (send_packet(srv_link, 2, seq_s++, sz) == 0) remaining_s -= sz; + if (send_etcp_packet(srv_conn, 2, seq_s++, sz) == 0) remaining_s -= sz; } if (remaining_s == 0 && seq_s > 0) srv_done = 1; if (!cli_done && remaining_c > 0) { size_t sz = PKT_MIN + (rand() % (PKT_MAX - PKT_MIN + 1)); if (sz > remaining_c) sz = remaining_c; - if (send_packet(cli_link, 1, seq_c++, sz) == 0) remaining_c -= sz; + if (send_etcp_packet(cli_conn, 1, seq_c++, sz) == 0) remaining_c -= sz; } if (remaining_c == 0 && seq_c > 0) cli_done = 1; @@ -230,8 +220,6 @@ int main(void) { TASSERT(srv_rx.len >= TRAFFIC_MB); TASSERT(cli_rx.len >= TRAFFIC_MB); - stcp_link_close(cli_link); - if (srv_link) stcp_link_close(srv_link); if (srv_rx.buf) u_free(srv_rx.buf); if (cli_rx.buf) u_free(cli_rx.buf);