diff --git a/AGENTS.md b/AGENTS.md index 5dcf706a..02ea4284 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -324,6 +324,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20 ### Эффективная диагностика: 0. Сосредоточься на поиске конкретной ошибки и доведи его до конца. Лучший путь - добавить диагностические сообщения/хуки/дополнительные проверки которые упростят поск бага и покажут где именно имкать проблему. + важно: нельзя вносить исправления только неа основе рассуждений - без точной диагностики бага в рантайме. сперва собираем отладочную информацию которая выявит проблемы путём добавления отладочного вывода, запуска, корректровки отладочного вывода по результатам. + и только когда будет достаточно данных для очевидного доказательства проблемы - исправляем и проверяем. исправление без очевидного доказательства запрещено! 1. Прочитай полностью код функций с ошибкой и код всех функции которые участвуют в ошибочном алгоритме 2. Мысленно выполни предполагаемый сценарий ошибки (нельзя додумывать - нужна точность): - Убедись что точно понимаешь как алгоритм приходит к ошибке diff --git a/lib/mem.c b/lib/mem.c index 9851c04d..ffc781fe 100644 --- a/lib/mem.c +++ b/lib/mem.c @@ -173,6 +173,8 @@ void u_free_impl(void* ptr, const char* location) { if (!ptr) return; u_check(ptr, "u_free", location); uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE; + char* alloc_loc = (char*)base; + uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4); // Remove from linked list (thread-safe) LOCK(); void* prev = NULL; @@ -193,9 +195,42 @@ void u_free_impl(void* ptr, const char* location) { prev = curr; curr = *(void**)((uint8_t*)curr + NEXT_OFFSET); } + size_t active_count = allocated_count; UNLOCK(); if (!found) { - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: u_free: Attempt to free unallocated or already freed pointer %p", location, ptr); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================"); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED"); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================"); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Free attempt at: %s", location); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Pointer: %p", ptr); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Base: %p", (void*)base); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Allocated at: %s", alloc_loc); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "User size: %u bytes", size); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Active allocs: %zu", active_count); + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8)); + hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8); + if (size > 0 && size <= 1024 * 1024) { + uint32_t dump_size = (size > 256) ? 256 : size; + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size); + hex_dump(ptr, dump_size); + } +#if BACKTRACE_ENABLED + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Backtrace ---"); +# if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) + void* bt_buf[32]; + int bt_size = backtrace(bt_buf, 32); + char** bt_symbols = backtrace_symbols(bt_buf, bt_size); + if (bt_symbols) { + for (int i = 0; i < bt_size; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %s", i, bt_symbols[i]); + free(bt_symbols); + } +# elif defined(_WIN32) + void* bt_buf[32]; + USHORT frames = CaptureStackBackTrace(0, 32, bt_buf, NULL); + for (USHORT i = 0; i < frames; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %p", i, bt_buf[i]); +# endif +#endif exit(EXIT_FAILURE); } free(base); diff --git a/src/lwip_tcp/lwip_tcp.c b/src/lwip_tcp/lwip_tcp.c index 3b5a1b88..4e75307f 100644 --- a/src/lwip_tcp/lwip_tcp.c +++ b/src/lwip_tcp/lwip_tcp.c @@ -12,6 +12,8 @@ // protocol constants #define NUM_TCP_PCB_LISTS 4 +tcp_trace_fn g_tcp_trace = NULL; + // window update threshold #ifndef TCP_WND_UPDATE_THRESHOLD #define TCP_WND_UPDATE_THRESHOLD (TCP_WND / 4) @@ -101,13 +103,31 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void return NULL; } ctx->iss_seed = (uint16_t)(get_time_tb() & 0xFFFF); - ctx->timer = uasync_set_timeout(ua, TCP_TMR_INTERVAL * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr"); + ctx->tmr_interval_ms = TCP_TMR_INTERVAL; + ctx->rto_min_ms = 3000; + ctx->rto_max_ms = 0; + ctx->timer = uasync_set_timeout(ua, ctx->tmr_interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr"); return ctx; } // ============================================ -// 2. lwip_tcp_destroy +// 2. lwip_tcp_set_timer +// ============================================ + +void lwip_tcp_set_timer(struct lwip_tcp_ctx *ctx, + uint16_t interval_ms, uint16_t rto_min_ms, uint16_t rto_max_ms) +{ + if (!ctx || interval_ms < 1) return; + ctx->tmr_interval_ms = interval_ms; + ctx->rto_min_ms = rto_min_ms; + ctx->rto_max_ms = rto_max_ms; + if (ctx->timer) uasync_cancel_timeout(ctx->ua, ctx->timer); + ctx->timer = uasync_set_timeout(ctx->ua, interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr"); +} + +// ============================================ +// 3. lwip_tcp_destroy // ============================================ void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx) @@ -144,15 +164,17 @@ static void tcp_tmr_cb(void *arg) struct lwip_tcp_ctx *ctx = (struct lwip_tcp_ctx *)arg; if (!ctx) return; + if (g_tcp_trace) g_tcp_trace(ctx, 'M', 0, 0, 0, "tmr"); + tcp_fasttmr(ctx); - ctx->slowtmr_ctr++; - if (ctx->slowtmr_ctr & 1) { + ctx->tmr_phase++; + if (ctx->tmr_phase & 1) { tcp_slowtmr(ctx); } - // re-arm — runs every TCP_TMR_INTERVAL ms - ctx->timer = uasync_set_timeout(ctx->ua, TCP_TMR_INTERVAL * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr"); + // re-arm + ctx->timer = uasync_set_timeout(ctx->ua, ctx->tmr_interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr"); } @@ -435,6 +457,7 @@ struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb) lpcb = (struct tcp_pcb_listen *)memory_pool_alloc(pcb->ctx->pcb_listen_pool); if (lpcb == NULL) { + pcb->ctx->stats.pcb_listen_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen: out of memory"); return NULL; } @@ -663,6 +686,8 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx) if ((pcb->rtime >= 0) && (pcb->rtime < 0x7FFF)) ++pcb->rtime; if (pcb->rtime >= pcb->rto) { + lwip_tcp_trace_record(pcb->ctx, 'T', pcb->snd_nxt, 0, pcb->cwnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); if ((tcp_rexmit_rto_prepare(pcb) == LERR_OK) || ((pcb->unacked == NULL) && (pcb->unsent != NULL))) { if (pcb->state != SYN_SENT) { uint8_t backoff_idx = LWIP_MIN(pcb->nrtx, sizeof(tcp_backoff) - 1); @@ -735,6 +760,9 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx) last_state = pcb->state; pcb2 = pcb; pcb = pcb->next; + lwip_tcp_trace_record(ctx, 'K', pcb2->snd_nxt, pcb2->nrtx, + pcb2->state, (uint16_t)pcb2->rto, pcb2->rtime, + (uint8_t)pcb2->local_port); tcp_free(pcb2); TCP_EVENT_ERR(last_state, err_fn, err_arg, LERR_ABRT); @@ -1033,6 +1061,10 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio) if (pcb == NULL) { tcp_kill_prio(ctx, prio); pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool); + if (pcb == NULL) { + ctx->stats.pcb_fails++; + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_alloc: out of memory after all retries"); + } } } } @@ -1199,3 +1231,92 @@ err_t tcp_tcp_get_tcp_addrinfo(struct tcp_pcb *pcb, int local, uint32_t *addr, u } return LERR_OK; } + +// ============================================ +// 29. Trace ring buffer +// ============================================ + +void lwip_tcp_trace_record(struct lwip_tcp_ctx *ctx, char event, uint32_t seq, + uint16_t len, uint16_t wnd, uint16_t rto, int16_t rtime, uint8_t state) +{ + if (!ctx) return; + struct tcp_trace_buf *b = &ctx->trace; + struct tcp_trace_entry *e = &b->e[b->pos]; + e->ticks = ctx->ticks; + e->event = event; + e->seq = seq; + e->len = len; + e->wnd = wnd; + e->rto = rto; + e->rtime = rtime; + e->ctx_id = (uint8_t)ctx->trace_id; + e->state = state; + b->pos = (b->pos + 1) % TCP_TRACE_SIZE; + if (b->count < TCP_TRACE_SIZE) b->count++; +} + +void lwip_tcp_trace_dump(struct lwip_tcp_ctx *ctx) +{ + if (!ctx) return; + struct tcp_trace_buf *b = &ctx->trace; + int n = b->count; + if (n == 0) return; + int start = (b->count < TCP_TRACE_SIZE) ? 0 : b->pos; + int show = n > 50 ? 50 : n; + int skip = n - show; + printf("=== Trace dump ctx=%c (%d events, showing last %d) ===\n", + ctx->trace_id ? ctx->trace_id : '?', n, show); + if (skip > 0) printf(" (skipped %d events)\n", skip); + for (int i = 0; i < show; i++) { + int idx = (start + skip + i) % TCP_TRACE_SIZE; + struct tcp_trace_entry *e = &b->e[idx]; + char buf[64]; + int di = snprintf(buf, sizeof(buf), "t=%05u %c %c", e->ticks, e->ctx_id, e->event); + switch (e->event) { + case 'S': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u len=%u wnd=%u", e->seq, e->len, e->wnd); break; + case 'X': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u len=%u cwnd=%u", e->seq, e->len, e->wnd); break; + case 'A': di += snprintf(buf + di, sizeof(buf) - di, " ack=%u acked=%u cwnd=%u", e->seq, e->len, e->wnd); break; + case 'D': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u len=%u", e->seq, e->len); break; + case 'r': di += snprintf(buf + di, sizeof(buf) - di, " rtime=%d rto=%u st=%u", e->rtime, e->rto, e->state); break; + case 'T': di += snprintf(buf + di, sizeof(buf) - di, " rto=%u rtime=%d", e->rto, e->rtime); break; + case 'R': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u len=%u rto=%u", e->seq, e->len, e->rto); break; + case 'F': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u len=%u st=%u", e->seq, e->len, e->wnd); break; + case 'C': di += snprintf(buf + di, sizeof(buf) - di, " st=%u", e->state); break; + case 'L': di += snprintf(buf + di, sizeof(buf) - di, " drop len=%u", e->len); break; + case 'M': break; + case 'K': di += snprintf(buf + di, sizeof(buf) - di, " st=%u nrtx=%u port=%u", + e->wnd, e->len, e->state); break; + case 'Y': di += snprintf(buf + di, sizeof(buf) - di, " seq=%u nrtx=%u", + e->seq, e->wnd); break; + case 'Z': di += snprintf(buf + di, sizeof(buf) - di, " st=%u reset=%u port=%u", + e->wnd, e->len, e->state); break; + case 'E': di += snprintf(buf + di, sizeof(buf) - di, " rc=%d", (int)e->seq); break; + case 'P': di += snprintf(buf + di, sizeof(buf) - di, " probe rto=%u", e->rto); break; + default: break; + } + printf(" %s\n", buf); + } +} + +void lwip_tcp_trace_clear(struct lwip_tcp_ctx *ctx) +{ + if (!ctx) return; + memset(&ctx->trace, 0, sizeof(ctx->trace)); +} + +void lwip_tcp_stats_dump(struct lwip_tcp_ctx *ctx) +{ + if (!ctx) return; + printf("=== Stats ctx=%c: pbuf=%u seg=%u pcb=%u listen=%u write=%u enq=%u rst=%u ack=%u probe=%u split=%u ===\n", + ctx->trace_id ? ctx->trace_id : '?', + ctx->stats.pbuf_fails, ctx->stats.seg_fails, ctx->stats.pcb_fails, + ctx->stats.pcb_listen_fails, ctx->stats.write_fails, ctx->stats.enq_fails, + ctx->stats.rst_fails, ctx->stats.ack_fails, ctx->stats.probe_fails, + ctx->stats.split_fails); +} + +void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx) +{ + if (!ctx) return; + memset(&ctx->stats, 0, sizeof(ctx->stats)); +} diff --git a/src/lwip_tcp/lwip_tcp.h b/src/lwip_tcp/lwip_tcp.h index 9fb31cfe..a75b7eed 100644 --- a/src/lwip_tcp/lwip_tcp.h +++ b/src/lwip_tcp/lwip_tcp.h @@ -83,6 +83,26 @@ typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *pcb, err_t err); // Output callback — called when TCP wants to send an IP packet typedef err_t (*tcp_output_fn)(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip); +#define TCP_TRACE_SIZE 384 + +struct tcp_trace_entry { + uint32_t ticks; + char event; + uint32_t seq; + uint16_t len; + uint16_t wnd; + uint16_t rto; + int16_t rtime; + uint8_t ctx_id; + uint8_t state; +}; + +struct tcp_trace_buf { + struct tcp_trace_entry e[TCP_TRACE_SIZE]; + int pos; + int count; +}; + // PCB structures struct tcp_pcb { struct tcp_pcb *next; @@ -181,16 +201,39 @@ struct lwip_tcp_ctx { struct memory_pool *pcb_listen_pool; struct memory_pool *seg_pool; + uint16_t tmr_interval_ms; // default 250 (was TCP_TMR_INTERVAL) + uint16_t rto_min_ms; // default 3000 (initial RTO in ms) + uint16_t rto_max_ms; // default 0 (0=unlimited, cap in ms) + uint32_t ticks; - uint8_t slowtmr_ctr; + uint8_t slowtmr_ctr; // for last_timer check (incremented by fasttmr and slowtmr) + uint8_t tmr_phase; // for even/odd slowtmr decision (incremented in tcp_tmr_cb) uint16_t iss_seed; uint16_t ip_id; uint16_t port_seed; + + char trace_id; // 'A' or 'B' for trace dump identification + struct tcp_trace_buf trace; + + struct { + uint32_t pbuf_fails; // pbuf_alloc → NULL + uint32_t seg_fails; // tcp_create_segment → NULL (seg pool) + uint32_t pcb_fails; // tcp_alloc → NULL (pcb pool) + uint32_t pcb_listen_fails; // tcp_listen pool alloc fail + uint32_t write_fails; // tcp_write → ERR_MEM + uint32_t enq_fails; // tcp_enqueue_flags → ERR_MEM + uint32_t rst_fails; // tcp_rst pbuf alloc fail + uint32_t ack_fails; // tcp_send_empty_ack pbuf fail + uint32_t probe_fails; // tcp_zero_window_probe pbuf fail + uint32_t split_fails; // tcp_split_unsent_seg fail + } stats; }; // Lifecycle struct lwip_tcp_ctx* lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void *output_arg); void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx); +void lwip_tcp_set_timer(struct lwip_tcp_ctx *ctx, + uint16_t interval_ms, uint16_t rto_min_ms, uint16_t rto_max_ms); // Input: feed an IP packet payload (after IP header) to TCP void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p, diff --git a/src/lwip_tcp/lwip_tcp_in.c b/src/lwip_tcp/lwip_tcp_in.c index f1e4a53a..88bb446d 100644 --- a/src/lwip_tcp/lwip_tcp_in.c +++ b/src/lwip_tcp/lwip_tcp_in.c @@ -67,7 +67,7 @@ static struct tcp_seg *tcp_seg_copy_local(struct tcp_pcb *pcb, struct tcp_seg *s struct tcp_seg *cseg; if (!seg || !pcb || !pcb->ctx) return NULL; cseg = (struct tcp_seg *)memory_pool_alloc(pcb->ctx->seg_pool); - if (!cseg) return NULL; + if (!cseg) { pcb->ctx->stats.seg_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_seg_copy_local: no memory"); return NULL; } memcpy(cseg, seg, sizeof(struct tcp_seg)); pbuf_ref(cseg->p); return cseg; @@ -266,6 +266,13 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p, tcp_input_pcb = pcb; err = tcp_process(pcb); + + if (recv_flags & TF_GOT_FIN && pcb->refused_data == NULL) { + if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) { + pcb->rcv_wnd++; + } + } + if (err != LERR_ABRT) { if (recv_flags & TF_RESET) { TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, LERR_RST); @@ -299,7 +306,6 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p, if (pcb->refused_data != NULL) { pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN; } else { - if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) pcb->rcv_wnd++; TCP_EVENT_CLOSED(pcb, err); if (err == LERR_ABRT) goto aborted; } @@ -539,6 +545,8 @@ static err_t tcp_process(struct tcp_pcb *pcb) dport, sport); } } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) { + lwip_tcp_trace_record(pcb->ctx, 'Y', seqno, 0, pcb->nrtx, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); tcp_rexmit(pcb); } break; @@ -728,6 +736,9 @@ static void tcp_receive(struct tcp_pcb *pcb) pcb->dupacks = 0; pcb->lastack = ackno; + lwip_tcp_trace_record(pcb->ctx, 'A', ackno, (uint16_t)acked, pcb->cwnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); + if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'A', ackno, (uint16_t)acked, pcb->cwnd, "ack"); if (pcb->state >= ESTABLISHED) { if (pcb->cwnd < pcb->ssthresh) { @@ -855,6 +866,11 @@ static void tcp_receive(struct tcp_pcb *pcb) pcb->rcv_wnd = (tcpwnd_size_t)(pcb->rcv_wnd - tcplen); tcp_update_rcv_ann_wnd(pcb); + if (inseg.p->tot_len > 0) { + lwip_tcp_trace_record(pcb->ctx, 'D', seqno, inseg.p->tot_len, pcb->cwnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); + if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'D', seqno, inseg.p->tot_len, pcb->cwnd, "data"); + } if (inseg.p->tot_len > 0) { recv_data = inseg.p; diff --git a/src/lwip_tcp/lwip_tcp_out.c b/src/lwip_tcp/lwip_tcp_out.c index a42d559c..600489b9 100644 --- a/src/lwip_tcp/lwip_tcp_out.c +++ b/src/lwip_tcp/lwip_tcp_out.c @@ -74,6 +74,17 @@ static void tcp_segs_free_local(struct tcp_pcb *pcb, struct tcp_seg *seg) // tcp_pbuf_prealloc — simplified (no oversize) #define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length)) +// pbuf alloc wrapper with error counting +static struct pbuf *tcp_pbuf_alloc(struct tcp_pcb *pcb, uint16_t layer, uint16_t length) +{ + struct pbuf *p = pbuf_alloc(layer, length); + if (!p && pcb && pcb->ctx) { + pcb->ctx->stats.pbuf_fails++; + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_pbuf_alloc: out of memory (layer=%u len=%u)", layer, length); + } + return p; +} + // Forward static err_t tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb); @@ -89,7 +100,8 @@ tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, seg = (struct tcp_seg *)memory_pool_alloc(pcb->ctx->seg_pool); if (seg == NULL) { - DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_create_segment: no memory"); + pcb->ctx->stats.seg_fails++; + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_create_segment: no memory for seg"); pbuf_free(p); return NULL; } @@ -127,12 +139,16 @@ tcp_write_checks(struct tcp_pcb *pcb, uint16_t len) } if (len == 0) return LERR_OK; if (len > pcb->snd_buf) { - DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: too much data"); + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: too much data (len=%u > snd_buf=%u)", len, pcb->snd_buf); + pcb->ctx->stats.write_fails++; + tcp_set_flags(pcb, TF_NAGLEMEMERR); tcp_set_flags(pcb, TF_NAGLEMEMERR); return LERR_MEM; } if (pcb->snd_queuelen >= TCP_SND_QUEUELEN) { - DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: too long queue"); + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: too long queue (queuelen=%u max=%u)", pcb->snd_queuelen, TCP_SND_QUEUELEN); + pcb->ctx->stats.write_fails++; + tcp_set_flags(pcb, TF_NAGLEMEMERR); tcp_set_flags(pcb, TF_NAGLEMEMERR); return LERR_MEM; } @@ -307,6 +323,7 @@ tcp_write(struct tcp_pcb *pcb, const void *arg, uint16_t len, uint8_t apiflags) return LERR_OK; memerr: + pcb->ctx->stats.write_fails++; tcp_set_flags(pcb, TF_NAGLEMEMERR); if (concat_p != NULL) pbuf_free(concat_p); if (queue != NULL) tcp_segs_free_local(pcb, queue); @@ -340,6 +357,7 @@ tcp_split_unsent_seg(struct tcp_pcb *pcb, uint16_t split) p = pbuf_alloc(PBUF_TRANSPORT, (uint16_t)(remainder + optlen)); if (p == NULL) { + pcb->ctx->stats.split_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_split_unsent_seg: could not allocate pbuf"); goto memerr; } @@ -420,14 +438,18 @@ tcp_enqueue_flags(struct tcp_pcb *pcb, uint8_t flags) } optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb); - p = pbuf_alloc(PBUF_TRANSPORT, optlen); + p = tcp_pbuf_alloc(pcb, PBUF_TRANSPORT, optlen); if (p == NULL) { + pcb->ctx->stats.enq_fails++; + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_enqueue_flags: pbuf_alloc failed (optlen=%u)", optlen); tcp_set_flags(pcb, TF_NAGLEMEMERR); return LERR_MEM; } seg = tcp_create_segment(pcb, p, flags, pcb->snd_lbb, optflags); if (seg == NULL) { + pcb->ctx->stats.enq_fails++; + DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_enqueue_flags: tcp_create_segment failed"); tcp_set_flags(pcb, TF_NAGLEMEMERR); return LERR_MEM; } @@ -479,6 +501,9 @@ tcp_output(struct tcp_pcb *pcb) if (pcb->local_ip == 0) return LERR_RTE; if ((ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len) > wnd) { + lwip_tcp_trace_record(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); + if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd, "cwd"); DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u", ntohl(seg->tcphdr->seqno) - pcb->lastack, seg->len, wnd, pcb->cwnd, pcb->snd_wnd); if (wnd == pcb->snd_wnd && pcb->unacked == NULL && pcb->persist_backoff == 0) { @@ -506,6 +531,9 @@ tcp_output(struct tcp_pcb *pcb) } err = tcp_output_segment(seg, pcb); + lwip_tcp_trace_record(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); + if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd, "snt"); DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d", ntohl(seg->tcphdr->seqno), seg->len, wnd, pcb->cwnd, err); if (err != LERR_OK) { @@ -656,7 +684,12 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb) seg->tcphdr->chksum = 0; seg->tcphdr->chksum = htons(tcp_pseudo_checksum(pcb->local_ip, pcb->remote_ip, - IP_PROTO_TCP, seg->p->tot_len, seg->p)); + IP_PROTO_TCP, seg->p->tot_len, seg->p)); + + if (TCPH_FLAGS(seg->tcphdr) & TCP_FIN) { + lwip_tcp_trace_record(pcb->ctx, 'C', ntohl(seg->tcphdr->seqno), seg->len, + pcb->snd_wnd, (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); + } if (pbuf_header(seg->p, (int16_t)sizeof(struct ip_hdr)) == 0) { iph = (struct ip_hdr *)seg->p->payload; @@ -686,7 +719,6 @@ err_t tcp_rexmit_rto_prepare(struct tcp_pcb *pcb) { struct tcp_seg *seg; - if (pcb == NULL) return LERR_VAL; if (pcb->unacked == NULL) return LERR_VAL; @@ -713,6 +745,8 @@ tcp_rexmit_rto_commit(struct tcp_pcb *pcb) { if (pcb == NULL) return; if (pcb->nrtx < 0xFF) pcb->nrtx++; + lwip_tcp_trace_record(pcb->ctx, 'R', pcb->lastack + 1, 0, pcb->cwnd, + (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); tcp_output(pcb); } @@ -763,6 +797,8 @@ tcp_rexmit_fast(struct tcp_pcb *pcb) if (pcb == NULL) return; if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) { if (tcp_rexmit(pcb) == LERR_OK) { + lwip_tcp_trace_record(pcb->ctx, 'F', ntohl(pcb->unacked->tcphdr->seqno), + pcb->unacked->len, pcb->ssthresh, (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); pcb->ssthresh = pcb->cwnd; if ((uint32_t)pcb->snd_wnd < pcb->cwnd) pcb->ssthresh = pcb->snd_wnd; pcb->ssthresh = (tcpwnd_size_t)(pcb->ssthresh / 2); @@ -909,6 +945,7 @@ tcp_rst(struct tcp_pcb *pcb, uint32_t seqno, uint32_t ackno, p = tcp_output_alloc_header_common(ackno, optlen, 0, htonl(seqno), local_port, remote_port, TCP_RST | TCP_ACK, wnd); if (p == NULL) { + if (pcb) pcb->ctx->stats.rst_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_rst: could not allocate memory for pbuf"); return; } @@ -932,6 +969,7 @@ tcp_send_empty_ack(struct tcp_pcb *pcb) p = tcp_output_alloc_header(pcb, optlen, 0, htonl(pcb->snd_nxt)); if (p == NULL) { + pcb->ctx->stats.ack_fails++; tcp_set_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW); DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_output: (ACK) could not allocate pbuf"); return LERR_BUF; @@ -978,12 +1016,15 @@ tcp_zero_window_probe(struct tcp_pcb *pcb) if (seg == NULL) return LERR_OK; if (pcb->persist_probe < 0xFF) pcb->persist_probe++; + lwip_tcp_trace_record(pcb->ctx, 'P', ntohl(seg->tcphdr->seqno), seg->len, + 0, (uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state); is_fin = ((TCPH_FLAGS(seg->tcphdr) & TCP_FIN) != 0) && (seg->len == 0); len = is_fin ? 0 : 1; p = tcp_output_alloc_header(pcb, optlen, len, seg->tcphdr->seqno); if (p == NULL) { + pcb->ctx->stats.probe_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_zero_window_probe: no memory for pbuf"); return LERR_MEM; } diff --git a/src/lwip_tcp/lwip_tcp_priv.h b/src/lwip_tcp/lwip_tcp_priv.h index 433aeff9..eafcb3c1 100644 --- a/src/lwip_tcp/lwip_tcp_priv.h +++ b/src/lwip_tcp/lwip_tcp_priv.h @@ -222,6 +222,17 @@ uint32_t tcp_next_iss(struct tcp_pcb *pcb); uint16_t tcp_checksum(const void *data, uint16_t len); uint16_t tcp_ip_checksum(const struct ip_hdr *iph); +// Trace hook — set by test to capture internal TCP events (ring buffer) +typedef void (*tcp_trace_fn)(struct lwip_tcp_ctx *ctx, char event, uint32_t seq, uint16_t len, uint16_t wnd, const char *msg); +extern tcp_trace_fn g_tcp_trace; + +void lwip_tcp_trace_record(struct lwip_tcp_ctx *ctx, char event, uint32_t seq, + uint16_t len, uint16_t wnd, uint16_t rto, int16_t rtime, uint8_t state); +void lwip_tcp_trace_dump(struct lwip_tcp_ctx *ctx); +void lwip_tcp_trace_clear(struct lwip_tcp_ctx *ctx); +void lwip_tcp_stats_dump(struct lwip_tcp_ctx *ctx); +void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx); + // PCB list management #define TCP_REG(pcbs, npcb) do { \ (npcb)->next = *(pcbs); *(pcbs) = (npcb); \ diff --git a/tests/Makefile.am b/tests/Makefile.am index b5579ced..38bb0e3a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -32,6 +32,7 @@ check_PROGRAMS = \ test_nat_stress \ test_etcp_reinit_inflight \ test_tcp_proxy \ + test_lwip_tcp \ test_etcp_router \ test_remote_proxy \ test_udp_proxy \ @@ -197,6 +198,15 @@ test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FUL test_tcp_proxy_SOURCES = test_tcp_proxy.c test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) +test_lwip_tcp_SOURCES = test_lwip_tcp.c +test_lwip_tcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_lwip_tcp_LDADD = \ + $(top_builddir)/src/lwip_tcp/utun-lwip_pbuf.o \ + $(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \ + $(top_builddir)/src/lwip_tcp/utun-lwip_tcp_in.o \ + $(top_builddir)/src/lwip_tcp/utun-lwip_tcp_out.o \ + $(COMMON_LIBS) + test_etcp_router_SOURCES = test_etcp_router.c test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) diff --git a/tests/test_lwip_tcp.c b/tests/test_lwip_tcp.c new file mode 100644 index 00000000..0f63bb11 --- /dev/null +++ b/tests/test_lwip_tcp.c @@ -0,0 +1,666 @@ +// test_lwip_tcp.c — lwIP TCP stress tests +// ===== 1. Types ===== +#include +#include +#include +#include "../src/lwip_tcp/lwip_tcp.h" +#include "../src/lwip_tcp/lwip_tcp_priv.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" + +#define DROP_PCT 0 // 0=fast, 10=stress test with 10% packet loss +#define TEST1_ITERS 3 +#define TEST2_CYCLES 5 +#define TEST2_FLOWS 3 +#define TEST1_PORT 10000 +#define TEST2_PORT 10010 +#define MAX_BLOCK 1500 +#define MIN_BLOCK 10 +#define IP_A 0x0A000001u +#define IP_B 0x0A000002u +#define PROGRESS_MS 500 +#define PROGRESS_TB (PROGRESS_MS * 10) +#define POLL_TB 100 + +struct conn_state; +struct pkt_node { struct pbuf *p; uint32_t s, d; struct pkt_node *n; }; + +struct tcp_bridge { + struct UASYNC *ua; + struct lwip_tcp_ctx *a, *b; + int drop_pct; + struct pkt_node *qa_h, *qa_t; + struct pkt_node *qb_h, *qb_t; + void *pt; + int tn, iter, total; + struct conn_state **st; + int sc, ad; + int64_t t0; // iteration start timestamp (ms) + int stuck_cnt; // consecutive no-progress count + size_t last_td, last_rd; // previous progress values + int dumped; // already dumped trace +}; + +struct conn_state { + struct tcp_pcb *pcb; + struct tcp_bridge *br; + int sd; // 0=server(A), 1=client(B) + uint8_t *sbuf, *rbuf; + size_t stot, soff, rtot, roff; + int sdone, rdone; // sent/received all + int err; // error flag + void *rt; // retry timer handle + uint8_t *ebuf; // echo buffer (server only) + size_t elen, ewr, ecap; +}; + +// ===== 2. Bridge ===== +static void q_add(struct pkt_node **h, struct pkt_node **t, struct pbuf *p, uint32_t s, uint32_t d) +{ + struct pkt_node *e = u_malloc(sizeof(*e)); + if (!e) { pbuf_free(p); return; } + e->p = p; e->s = s; e->d = d; e->n = NULL; + if (*t) { (*t)->n = e; *t = e; } else { *h = *t = e; } +} + +static void q_flush(struct lwip_tcp_ctx *ctx, struct pkt_node **h, struct pkt_node **t) +{ + struct pkt_node *e; + while ((e = *h)) { *h = e->n; lwip_tcp_input(ctx, e->p, e->s, e->d); u_free(e); } + *t = NULL; +} + +static void bridge_deliver(void *arg) +{ + struct tcp_bridge *br = arg; + q_flush(br->a, &br->qa_h, &br->qa_t); + q_flush(br->b, &br->qb_h, &br->qb_t); +} + +static err_t bridge_out(void *arg, struct pbuf *p, uint32_t s, uint32_t d, int to) +{ + struct tcp_bridge *br = arg; + uint16_t ih = (uint16_t)sizeof(struct ip_hdr); + uint16_t tl = p->tot_len - ih; + if (br->drop_pct > 0 && (rand() % 100) < br->drop_pct) { + struct tcp_hdr thdr_buf; + uint32_t seq = 0; + if (pbuf_copy_partial(p, &thdr_buf, sizeof(thdr_buf), ih) == sizeof(thdr_buf)) + seq = ntohl(thdr_buf.seqno); + struct lwip_tcp_ctx *ctx = (to == 0) ? br->b : br->a; + lwip_tcp_trace_record(ctx, 'L', seq, tl, 0, 0, 0, 0); + return LERR_OK; + } + struct pbuf *c = pbuf_alloc(PBUF_RAW, tl); + if (!c) return LERR_OK; + pbuf_copy_partial(p, c->payload, tl, ih); + if (to == 0) q_add(&br->qa_h, &br->qa_t, c, s, d); + else q_add(&br->qb_h, &br->qb_t, c, s, d); + return LERR_OK; +} + +static err_t bo_a(void *a, struct pbuf *p, uint32_t s, uint32_t d) { return bridge_out(a, p, s, d, 1); } +static err_t bo_b(void *a, struct pbuf *p, uint32_t s, uint32_t d) { return bridge_out(a, p, s, d, 0); } + +// ===== 3. Helpers ===== +static void retry_cb(void *arg); +static void check_close(struct conn_state *s) +{ + if (s->sdone && s->rdone) { tcp_close(s->pcb); } +} + +static int send_block(struct conn_state *s) +{ + while (s->soff < s->stot) { + size_t r = s->stot - s->soff; + uint16_t b = (r > MAX_BLOCK) ? MAX_BLOCK : (uint16_t)r; + if (b > MIN_BLOCK + 1) b = (uint16_t)(MIN_BLOCK + (uint16_t)(rand() % (b - MIN_BLOCK))); + if (b > tcp_sndbuf(s->pcb)) { + if (!s->rt) s->rt = uasync_set_timeout(s->br->ua, 20, s, retry_cb, "rt"); + return -1; + } + if (tcp_write(s->pcb, s->sbuf + s->soff, b, TCP_WRITE_FLAG_COPY) != LERR_OK) { + if (!s->rt) s->rt = uasync_set_timeout(s->br->ua, 20, s, retry_cb, "rt"); + return -1; + } + s->soff += b; + } + s->sdone = 1; + if (s->rtot == 0) s->rdone = 1; + if (s->sdone && s->rdone) check_close(s); + return 0; +} + +static void retry_cb(void *arg) +{ + struct conn_state *s = arg; + s->rt = NULL; + if (s->err || !s->pcb) return; + if (!s->sdone) send_block(s); +} + +static int sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) +{ + struct conn_state *s = arg; + (void)pcb; + if (s->rt) { uasync_cancel_timeout(s->br->ua, s->rt); s->rt = NULL; } + if (s->err) return LERR_OK; + if (!s->sdone) send_block(s); + // all data sent? close when recv is also done + if (s->sdone && s->rdone) check_close(s); + return LERR_OK; +} + +static void err_cb(void *arg, int e) +{ + struct conn_state *s = arg; + (void)e; + s->err = 1; + if (s->rt) { uasync_cancel_timeout(s->br->ua, s->rt); s->rt = NULL; } +} + +static void setup_pcb_server(struct conn_state *s, void *rf) +{ + tcp_arg(s->pcb, s); + tcp_recv(s->pcb, (tcp_recv_fn)rf); + tcp_err(s->pcb, (tcp_err_fn)err_cb); + tcp_nagle_disable(s->pcb); +} + +static void setup_pcb(struct conn_state *s, void *rf) +{ + tcp_arg(s->pcb, s); + tcp_recv(s->pcb, (tcp_recv_fn)rf); + tcp_sent(s->pcb, sent_cb); + tcp_err(s->pcb, (tcp_err_fn)err_cb); + tcp_nagle_disable(s->pcb); +} + +static char stc(struct conn_state *s) +{ + if (!s || !s->pcb) return '.'; + switch (s->pcb->state) { + case SYN_SENT: return 'S'; + case SYN_RCVD: return 'R'; + case ESTABLISHED: return 'E'; + case FIN_WAIT_1: return '1'; + case FIN_WAIT_2: return '2'; + case CLOSE_WAIT: return 'C'; + case CLOSING: return 'G'; + case LAST_ACK: return 'A'; + case TIME_WAIT: return 'W'; + case CLOSED: return 'T'; + default: return '?'; + } +} + +static void progress_timer(void *arg) +{ + struct tcp_bridge *br = arg; + double el = (double)((int64_t)(get_time_tb() / 10) - br->t0) / 1000.0; + + int done = 0, err = 0; + size_t ts = 0, td = 0, tr = 0, rd = 0; + + for (int i = 0; i < br->sc; i++) { + struct conn_state *c = br->st[i]; + if (!c) continue; + if (c->err) { err++; done++; } + else if (!c->pcb) { done++; } + else if (c->pcb->state >= FIN_WAIT_1) { done++; } + else if (c->sdone && c->rdone) { done++; } + else { ts += c->stot; td += c->soff; tr += c->rtot; rd += c->roff; } + } + + // build per-connection detail line + char detail[256] = ""; + int di = 0; + if (br->sc == 2) { + struct conn_state *a = br->st[0], *b = br->st[1]; + if (!a) { a = b; b = NULL; } + int a_ok = a && a->pcb, b_ok = b && b->pcb; + di += snprintf(detail + di, sizeof(detail) - di, " A=[%c", a_ok ? stc(a) : '.'); + if (a_ok) di += snprintf(detail + di, sizeof(detail) - di, " sd=%zu/%zu rd=%zu/%zu cw=%u ua=%d", + a->soff, a->stot, a->roff, a->rtot, (unsigned)a->pcb->cwnd, a->pcb->unacked ? 1 : 0); + di += snprintf(detail + di, sizeof(detail) - di, "]"); + di += snprintf(detail + di, sizeof(detail) - di, " B=[%c", b_ok ? stc(b) : '.'); + if (b_ok) di += snprintf(detail + di, sizeof(detail) - di, " sd=%zu/%zu rd=%zu/%zu cw=%u ua=%d", + b->soff, b->stot, b->roff, b->rtot, (unsigned)b->pcb->cwnd, b->pcb->unacked ? 1 : 0); + di += snprintf(detail + di, sizeof(detail) - di, "]"); + } else { + char st[64]; int si = 0; + for (int i = 0; i < br->sc && si < 60; i++) st[si++] = stc(br->st[i]); + st[si] = '\0'; + di += snprintf(detail + di, sizeof(detail) - di, " flows=%s sd=%zu/%zu rd=%zu/%zu", st, td, ts, rd, tr); + } + + printf(" Test%d #%d/%d | %5.1fs |%s\n", + br->tn, br->iter, br->total, el, detail); + + int stuck = 0; + if (done + err >= br->sc) { br->ad = 1; } + else if (ts == br->last_td && tr == br->last_rd) { + br->stuck_cnt++; + if (br->stuck_cnt >= 10 && !br->dumped) { + br->dumped = 1; + printf("=== STUCK for %.1fs, dumping trace ===\n", (float)br->stuck_cnt * PROGRESS_MS / 1000.0f); + printf(" Per-flow state:\n"); + for (int j = 0; j < br->sc; j++) { + struct conn_state *c = br->st[j]; + if (!c) continue; + printf(" [%d] %s pcb=%p st=%c so=%zu/%zu ro=%zu/%zu err=%d\n", + j, j%2==0?"sv":"cl", (void*)(c->pcb), stc(c), + c->soff, c->stot, c->roff, c->rtot, c->err); + if (c->pcb) printf(" pcb: lport=%u rport=%u cwnd=%u sndbuf=%u snd_wnd=%u rto=%d rtime=%d\n", + c->pcb->local_port, c->pcb->remote_port, c->pcb->cwnd, + (unsigned)tcp_sndbuf(c->pcb), c->pcb->snd_wnd, c->pcb->rto, c->pcb->rtime); + } + lwip_tcp_trace_dump(br->a); + lwip_tcp_trace_dump(br->b); + lwip_tcp_stats_dump(br->a); + lwip_tcp_stats_dump(br->b); + // NOTE: no forced exit — let RTO retransmission complete naturally + } + stuck = 1; + } else { + br->stuck_cnt = 0; + br->last_td = ts; + br->last_rd = tr; + br->dumped = 0; + } + if (!stuck || !br->dumped) + br->pt = uasync_set_timeout(br->ua, PROGRESS_TB, br, progress_timer, "pg"); +} + +// ===== 4. Test 1: bidirectional exchange ===== +static int t1_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, int e) +{ + struct conn_state *s = arg; + if (p) { + uint16_t l = p->tot_len; + if (s->roff + l > s->rtot) { s->err = 1; return LERR_OK; } + pbuf_copy_partial(p, s->rbuf + s->roff, l, 0); + s->roff += l; + tcp_recved(pcb, l); + if (s->roff == s->rtot) { s->rdone = 1; check_close(s); } + } else { + // FIN: close when both sides done + s->rdone = 1; + check_close(s); + } + return LERR_OK; +} + +static int t2_client_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, int e) +{ + struct conn_state *s = arg; + if (p) { + uint16_t l = p->tot_len; + if (s->roff + l > s->rtot) { s->err = 1; return LERR_OK; } + pbuf_copy_partial(p, s->rbuf + s->roff, l, 0); + s->roff += l; + tcp_recved(pcb, l); + if (s->roff == s->rtot) { s->rdone = 1; check_close(s); } + } else { + s->rdone = 1; + check_close(s); + } + return LERR_OK; +} + +static int t1_accept(void *arg, struct tcp_pcb *n, int e) +{ + struct tcp_bridge *br = arg; + if (e || !n) return LERR_ABRT; + struct conn_state *s = br->st[0]; + s->pcb = n; + setup_pcb(s, t1_recv); + send_block(s); + return LERR_OK; +} + +static int t1_connected(void *arg, struct tcp_pcb *pcb, int e) +{ + (void)pcb; + struct conn_state *s = arg; + if (e) { s->err = 1; return LERR_ABRT; } + send_block(s); + return LERR_OK; +} + +static void cleanup_lists(struct tcp_bridge *br) +{ + struct tcp_pcb *t; + while ((t = br->a->tw_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->a->tw_pcbs, t); tcp_free(t); } + while ((t = br->b->tw_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->b->tw_pcbs, t); tcp_free(t); } + while ((t = br->a->active_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->a->active_pcbs, t); tcp_free(t); } + while ((t = br->b->active_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->b->active_pcbs, t); tcp_free(t); } + while ((t = br->a->bound_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->a->bound_pcbs, t); tcp_free(t); } + while ((t = br->b->bound_pcbs)) { t->errf = NULL; t->callback_arg = NULL; TCP_RMV(&br->b->bound_pcbs, t); tcp_free(t); } + br->a->active_pcbs = br->a->tw_pcbs = br->a->bound_pcbs = NULL; + br->b->active_pcbs = br->b->tw_pcbs = br->b->bound_pcbs = NULL; +} + +static int run_test1(struct tcp_bridge *br) +{ + br->tn = 1; br->total = TEST1_ITERS; + int ok = 1; + + for (int iter = 0; iter < TEST1_ITERS && ok; iter++) { + br->iter = iter + 1; br->ad = 0; + br->stuck_cnt = 0; br->last_td = 0; br->last_rd = 0; br->dumped = 0; + lwip_tcp_trace_clear(br->a); + lwip_tcp_trace_clear(br->b); + lwip_tcp_stats_clear(br->a); + lwip_tcp_stats_clear(br->b); + br->t0 = (int64_t)(get_time_tb() / 10); + + uint16_t port = htons((uint16_t)(TEST1_PORT + iter)); + + // create listen PCB with accept callback + struct tcp_pcb *lp = tcp_new(br->a); + tcp_bind(lp, IP_A, port); + lp = tcp_listen(lp); + tcp_arg(lp, br); + tcp_accept(lp, (tcp_accept_fn)t1_accept); + + // create client conn_state with send/recv buffers + struct conn_state *sv = u_calloc(1, sizeof(*sv)); + sv->br = br; sv->sd = 0; + struct conn_state *cl = u_calloc(1, sizeof(*cl)); + cl->br = br; cl->sd = 1; + + size_t bs = (size_t)(4096 + rand() % 12288); + size_t bc = (size_t)(4096 + rand() % 12288); + printf(" Test1 #%d start: port=%d A→B=%zuB B→A=%zuB\n", + iter + 1, (int)ntohs(port), bs, bc); + sv->stot = bs; sv->sbuf = u_malloc(bs); + sv->rtot = bc; sv->rbuf = u_malloc(bc); + cl->stot = bc; cl->sbuf = u_malloc(bc); + cl->rtot = bs; cl->rbuf = u_malloc(bs); + for (size_t i = 0; i < bs; i++) sv->sbuf[i] = (uint8_t)(rand() & 0xFF); + for (size_t i = 0; i < bc; i++) cl->sbuf[i] = (uint8_t)(rand() & 0xFF); + + struct conn_state *states[2] = { sv, cl }; + br->st = states; br->sc = 2; + br->pt = uasync_set_timeout(br->ua, PROGRESS_TB, br, progress_timer, "pg"); + + // create client PCB and connect + cl->pcb = tcp_new(br->b); + tcp_bind(cl->pcb, IP_B, 0); + setup_pcb(cl, t1_recv); + tcp_connect(cl->pcb, IP_A, port, (tcp_connected_fn)t1_connected); + + // poll until done + while (!br->ad && !sv->err && !cl->err) { + uasync_poll(br->ua, POLL_TB); + bridge_deliver(br); + } + uasync_poll(br->ua, POLL_TB); + bridge_deliver(br); + + if (br->pt) { uasync_cancel_timeout(br->ua, br->pt); br->pt = NULL; } + + if (sv->err || cl->err) { + fprintf(stderr, "[FAIL] Test1 iter %d: error\n", iter); + ok = 0; + } + if (sv->soff != bs || cl->roff != bs) { + fprintf(stderr, "[FAIL] Test1 iter %d: A→B %zu/%zu\n", iter, cl->roff, bs); + ok = 0; + } + if (cl->soff != bc || sv->roff != bc) { + fprintf(stderr, "[FAIL] Test1 iter %d: B→A %zu/%zu\n", iter, sv->roff, bc); + ok = 0; + } + if (ok && memcmp(cl->rbuf, sv->sbuf, bs) != 0) { + fprintf(stderr, "[FAIL] Test1 iter %d: A→B mismatch\n", iter); + ok = 0; + } + if (ok && memcmp(sv->rbuf, cl->sbuf, bc) != 0) { + fprintf(stderr, "[FAIL] Test1 iter %d: B→A mismatch\n", iter); + ok = 0; + } + if (ok) printf(" Test1 #%d/%d PASS\n", iter + 1, TEST1_ITERS); + if (lp) tcp_close(lp); + u_free(sv->sbuf); u_free(sv->rbuf); u_free(sv); + u_free(cl->sbuf); u_free(cl->rbuf); u_free(cl); + cleanup_lists(br); + } + + printf(" Test1: %s\n", ok ? "PASS" : "FAIL"); + return ok ? 0 : 1; +} + +// ===== 5. Test 2: client-server echo ===== +static int t2_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, int e) +{ + struct conn_state *s = arg; + if (p) { + uint16_t l = p->tot_len; + uint8_t *tmp = u_malloc(l); + if (!tmp) { tcp_recved(pcb, l); pbuf_free(p); return LERR_OK; } + pbuf_copy_partial(p, tmp, l, 0); + tcp_recved(pcb, l); + pbuf_free(p); + + if (s->elen + l > s->ecap) { + size_t nc = s->ecap ? s->ecap * 2 : 65536; + while (nc < s->elen + l) nc *= 2; + uint8_t *nb = u_realloc(s->ebuf, nc); + if (!nb) { u_free(tmp); return LERR_OK; } + s->ebuf = nb; + s->ecap = nc; + } + memcpy(s->ebuf + s->elen, tmp, l); + u_free(tmp); + s->elen += l; + + while (s->ewr < s->elen) { + size_t rem = s->elen - s->ewr; + uint16_t c = (rem > MAX_BLOCK) ? MAX_BLOCK : (uint16_t)rem; + if (c > tcp_sndbuf(s->pcb)) break; + if (tcp_write(s->pcb, s->ebuf + s->ewr, c, TCP_WRITE_FLAG_COPY) != LERR_OK) break; + s->ewr += c; + } + tcp_output(s->pcb); + if (s->ewr == s->elen) { + u_free(s->ebuf); + s->ebuf = NULL; + s->elen = s->ewr = s->ecap = 0; + } + } else { + if (s->elen == 0 && s->pcb) tcp_close(s->pcb); + } + return LERR_OK; +} + +static int t2_accept(void *arg, struct tcp_pcb *n, int e) +{ + struct tcp_bridge *br = arg; + if (e || !n) return LERR_ABRT; + + for (int i = 0; i < br->sc; i += 2) { + struct conn_state *s = br->st[i]; + if (s && !s->pcb) { + s->pcb = n; + setup_pcb_server(s, t2_server_recv); + return LERR_OK; + } + } + return LERR_ABRT; +} + +static int t2_connected(void *arg, struct tcp_pcb *pcb, int e) +{ + (void)pcb; + struct conn_state *s = arg; + if (e) { s->err = 1; return LERR_ABRT; } + send_block(s); + return LERR_OK; +} + +static int run_test2(struct tcp_bridge *br) +{ + br->tn = 2; br->total = TEST2_CYCLES; + int ok = 1; + int N = TEST2_FLOWS; + + // create N listen PCBs once + struct tcp_pcb *ls[N]; + for (int i = 0; i < N; i++) { + struct tcp_pcb *l = tcp_new(br->a); + tcp_bind(l, IP_A, htons((uint16_t)(TEST2_PORT + i))); + ls[i] = tcp_listen(l); + tcp_arg(ls[i], br); + tcp_accept(ls[i], (tcp_accept_fn)t2_accept); + } + + for (int cy = 0; cy < TEST2_CYCLES && ok; cy++) { + br->iter = cy + 1; br->ad = 0; + br->stuck_cnt = 0; br->last_td = 0; br->last_rd = 0; br->dumped = 0; + lwip_tcp_trace_clear(br->a); + lwip_tcp_trace_clear(br->b); + lwip_tcp_stats_clear(br->a); + lwip_tcp_stats_clear(br->b); + br->t0 = (int64_t)(get_time_tb() / 10); + + struct conn_state **sv = u_calloc(N, sizeof(*sv)); + struct conn_state **cl = u_calloc(N, sizeof(*cl)); + struct conn_state **all = u_calloc(2 * N, sizeof(*all)); + + // create client PCBs and connect + for (int i = 0; i < N; i++) { + sv[i] = u_calloc(1, sizeof(**sv)); + sv[i]->br = br; sv[i]->sd = 0; sv[i]->err = 0; sv[i]->sdone = 0; sv[i]->rdone = 0; + all[2 * i] = sv[i]; + + cl[i] = u_calloc(1, sizeof(**cl)); + cl[i]->br = br; cl[i]->sd = 1; + cl[i]->err = 0; cl[i]->sdone = 0; cl[i]->rdone = 0; + all[2 * i + 1] = cl[i]; + + cl[i]->pcb = tcp_new(br->b); + tcp_bind(cl[i]->pcb, IP_B, 0); + setup_pcb(cl[i], t2_client_recv); + + // generate random data before connect + int nb = rand() % 31; + if (nb > 0) { + size_t tot = 0; + for (int b = 0; b < nb; b++) + tot += (size_t)(MIN_BLOCK + rand() % (MAX_BLOCK - MIN_BLOCK + 1)); + cl[i]->stot = tot; + cl[i]->sbuf = u_malloc(tot); + cl[i]->rtot = tot; + cl[i]->rbuf = u_malloc(tot); + for (size_t j = 0; j < tot; j++) + cl[i]->sbuf[j] = (uint8_t)(rand() & 0xFF); + } + } + + br->st = all; br->sc = 2 * N; + br->pt = uasync_set_timeout(br->ua, PROGRESS_TB, br, progress_timer, "pg"); + + // connect all (triggers handshake via bridge) + for (int i = 0; i < N; i++) { + uint16_t port = htons((uint16_t)(TEST2_PORT + i)); + tcp_connect(cl[i]->pcb, IP_A, port, (tcp_connected_fn)t2_connected); + } + + // poll until all done + while (!br->ad) { + uasync_poll(br->ua, POLL_TB); + bridge_deliver(br); + } + uasync_poll(br->ua, POLL_TB); + bridge_deliver(br); + + if (br->pt) { uasync_cancel_timeout(br->ua, br->pt); br->pt = NULL; } + + // verify + for (int i = 0; i < N && ok; i++) { + if (cl[i]->err) { + fprintf(stderr, "[FAIL] Test2 cy %d flow %d: error\n", cy, i); + ok = 0; + } + if (cl[i]->stot > 0 && + memcmp(cl[i]->rbuf, cl[i]->sbuf, cl[i]->stot) != 0) { + fprintf(stderr, "[FAIL] Test2 cy %d flow %d: mismatch\n", cy, i); + ok = 0; + } + } + if (ok && ((cy + 1) % 5 == 0 || cy == TEST2_CYCLES - 1)) + printf(" Test2 #%d/%d PASS\n", cy + 1, TEST2_CYCLES); + + // cleanup + for (int i = 0; i < N; i++) { + if (cl[i]) { + u_free(cl[i]->sbuf); u_free(cl[i]->rbuf); + u_free(cl[i]); + } + if (sv[i]) { + u_free(sv[i]->ebuf); + u_free(sv[i]); + } + } + cleanup_lists(br); + u_free(sv); u_free(cl); u_free(all); + } + + for (int i = 0; i < N; i++) + if (ls[i]) tcp_close(ls[i]); + + if (!ok) { lwip_tcp_stats_dump(br->a); lwip_tcp_stats_dump(br->b); } + printf(" Test2: %s\n", ok ? "PASS" : "FAIL"); + return ok ? 0 : 1; +} + +// ===== 6. main ===== +int main(int argc, char *argv[]) +{ + unsigned int seed; + if (argc >= 2) { + seed = (unsigned int)strtoul(argv[1], NULL, 0); + } else { + seed = (unsigned int)time(NULL); + } + srand(seed); + printf("seed=%u\n", seed); + debug_config_init(); + debug_set_console_level(DEBUG_LEVEL_NONE); + debug_set_category_level(DEBUG_CATEGORY_ALL, DEBUG_LEVEL_ERROR); + setlinebuf(stdout); + + struct UASYNC *ua = uasync_create(); + if (!ua) return 1; + + struct tcp_bridge *br = u_calloc(1, sizeof(*br)); + br->ua = ua; br->drop_pct = DROP_PCT; + br->a = lwip_tcp_init(ua, bo_a, br); + br->b = lwip_tcp_init(ua, bo_b, br); + if (!br->a || !br->b) { fprintf(stderr, "[FAIL] init\n"); return 1; } + br->a->trace_id = 'A'; br->b->trace_id = 'B'; + lwip_tcp_set_timer(br->a, 1, 10, 40); + lwip_tcp_set_timer(br->b, 1, 10, 40); + + int r1 = run_test1(br); + + // rebuild contexts for Test 2 — fresh memory pools + lwip_tcp_destroy(br->a); lwip_tcp_destroy(br->b); + br->a = lwip_tcp_init(ua, bo_a, br); + br->b = lwip_tcp_init(ua, bo_b, br); + if (!br->a || !br->b) { fprintf(stderr, "[FAIL] reinit\n"); return 1; } + br->a->trace_id = 'A'; br->b->trace_id = 'B'; + lwip_tcp_set_timer(br->a, 1, 10, 40); + lwip_tcp_set_timer(br->b, 1, 10, 40); + + int r2 = run_test2(br); + + lwip_tcp_destroy(br->a); lwip_tcp_destroy(br->b); + uasync_destroy(ua, 0); u_free(br); + + printf("\n%s\n", (r1 == 0 && r2 == 0) ? "[PASS] test_lwip_tcp" : "[FAIL] test_lwip_tcp"); + return (r1 == 0 && r2 == 0) ? 0 : 1; +}