You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1201 lines
33 KiB

// lwip_tcp.c — adapted lwIP TCP core module for uTun
// Context-based, single-threaded, uasync-driven
#include "lwip_tcp_opts.h"
#include "lwip_tcp_priv.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include "../../lib/memory_pool.h"
#include "../../lib/u_async.h"
#include <string.h>
// protocol constants
#define NUM_TCP_PCB_LISTS 4
// window update threshold
#ifndef TCP_WND_UPDATE_THRESHOLD
#define TCP_WND_UPDATE_THRESHOLD (TCP_WND / 4)
#endif
// helper macro for minimum
#ifndef LWIP_MIN
#define LWIP_MIN(a,b) ((a)<(b)?(a):(b))
#endif
// backoff arrays
const uint8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7 };
const uint8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 };
static const char *const tcp_state_str[] = {
"CLOSED", "LISTEN", "SYN_SENT", "SYN_RCVD", "ESTABLISHED",
"FIN_WAIT_1", "FIN_WAIT_2", "CLOSE_WAIT", "CLOSING", "LAST_ACK", "TIME_WAIT"
};
// ---- forward declarations ----
static void tcp_tmr_cb(void *arg);
static err_t tcp_close_shutdown_fin(struct tcp_pcb *pcb);
static uint16_t tcp_new_port(struct tcp_pcb *pcb);
static void tcp_kill_prio(struct lwip_tcp_ctx *ctx, uint8_t prio);
static void tcp_kill_state(struct lwip_tcp_ctx *ctx, enum tcp_state state);
static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx);
static void tcp_handle_closepend(struct lwip_tcp_ctx *ctx);
static err_t tcp_close_shutdown(struct tcp_pcb *pcb, uint8_t rst_on_unacked_data);
static void tcp_remove_listener(struct tcp_pcb *list, struct tcp_pcb_listen *lpcb);
static void tcp_free_listen(struct tcp_pcb *pcb);
// seg_free helpers — internal, use ctx for pool access
static void tcp_seg_free_local(struct lwip_tcp_ctx *ctx, struct tcp_seg *seg)
{
if (seg != NULL) {
if (seg->p != NULL) pbuf_free(seg->p);
memory_pool_free(ctx->seg_pool, seg);
}
}
static void tcp_segs_free_local(struct lwip_tcp_ctx *ctx, struct tcp_seg *seg)
{
while (seg != NULL) {
struct tcp_seg *next = seg->next;
tcp_seg_free_local(ctx, seg);
seg = next;
}
}
// ============================================
// 14. tcp_seg_free / tcp_segs_free
// ============================================
void tcp_seg_free(struct tcp_seg *seg)
{
// This stub exists for API compatibility; callers in tcp.c use
// tcp_seg_free_local(ctx, seg) with explicit pool context.
// The out.c module has its own tcp_seg_free_local(pcb, seg).
(void)seg;
}
void tcp_segs_free(struct tcp_seg *seg)
{
// Same as above — kept for API compatibility.
(void)seg;
}
// ============================================
// 1. lwip_tcp_init
// ============================================
struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void *output_arg)
{
struct lwip_tcp_ctx *ctx = u_calloc(1, sizeof(*ctx));
if (!ctx) return NULL;
ctx->ua = ua;
ctx->output = output;
ctx->output_arg = output_arg;
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb));
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen));
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg));
if (!ctx->pcb_pool || !ctx->pcb_listen_pool || !ctx->seg_pool) {
lwip_tcp_destroy(ctx);
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");
return ctx;
}
// ============================================
// 2. lwip_tcp_destroy
// ============================================
void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx)
{
if (!ctx) return;
// cancel pending timer
if (ctx->timer) {
uasync_cancel_timeout(ctx->ua, ctx->timer);
ctx->timer = NULL;
}
// free all PCBs in all lists (back-to-front, tcp_free adjusts list)
while (ctx->active_pcbs) tcp_abort(ctx->active_pcbs);
while (ctx->tw_pcbs) tcp_abort(ctx->tw_pcbs);
while (ctx->listen_pcbs) tcp_close(ctx->listen_pcbs);
while (ctx->bound_pcbs) tcp_close(ctx->bound_pcbs);
// destroy memory pools
if (ctx->pcb_pool) memory_pool_destroy(ctx->pcb_pool);
if (ctx->pcb_listen_pool) memory_pool_destroy(ctx->pcb_listen_pool);
if (ctx->seg_pool) memory_pool_destroy(ctx->seg_pool);
u_free(ctx);
}
// ============================================
// 3. tcp_tmr_cb (uasync callback)
// ============================================
static void tcp_tmr_cb(void *arg)
{
struct lwip_tcp_ctx *ctx = (struct lwip_tcp_ctx *)arg;
if (!ctx) return;
tcp_fasttmr(ctx);
ctx->slowtmr_ctr++;
if (ctx->slowtmr_ctr & 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");
}
// ============================================
// 5. tcp_free / tcp_free_listen
// ============================================
void tcp_free(struct tcp_pcb *pcb)
{
if (!pcb) return;
if (pcb->state == LISTEN) {
tcp_free_listen(pcb);
return;
}
memory_pool_free(pcb->ctx->pcb_pool, pcb);
}
static void tcp_free_listen(struct tcp_pcb *pcb)
{
if (!pcb) return;
memory_pool_free(pcb->ctx->pcb_listen_pool, pcb);
}
// ============================================
// 7. tcp_remove_listener / tcp_listen_closed
// ============================================
static void tcp_remove_listener(struct tcp_pcb *list, struct tcp_pcb_listen *lpcb)
{
struct tcp_pcb *pcb;
if (!lpcb) return;
for (pcb = list; pcb != NULL; pcb = pcb->next) {
if (pcb->listener == lpcb) {
pcb->listener = NULL;
}
}
}
static void tcp_listen_closed(struct tcp_pcb *pcb)
{
if (!pcb) return;
if (pcb->state != LISTEN) return;
struct lwip_tcp_ctx *ctx = pcb->ctx;
tcp_remove_listener(ctx->bound_pcbs, (struct tcp_pcb_listen *)pcb);
tcp_remove_listener(ctx->active_pcbs, (struct tcp_pcb_listen *)pcb);
tcp_remove_listener(ctx->tw_pcbs, (struct tcp_pcb_listen *)pcb);
}
// ============================================
// 8. tcp_close_shutdown / tcp_close_shutdown_fin / tcp_close
// ============================================
static err_t tcp_close_shutdown(struct tcp_pcb *pcb, uint8_t rst_on_unacked_data)
{
if (!pcb) return LERR_ARG;
if (rst_on_unacked_data && (pcb->state == ESTABLISHED || pcb->state == CLOSE_WAIT)) {
if (pcb->refused_data != NULL || pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, pcb->local_ip, pcb->remote_ip,
pcb->local_port, pcb->remote_port);
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb->ctx, pcb);
tcp_free(pcb);
return LERR_OK;
}
}
switch (pcb->state) {
case CLOSED:
if (pcb->local_port != 0) {
TCP_RMV(&pcb->ctx->bound_pcbs, pcb);
}
tcp_free(pcb);
break;
case LISTEN:
tcp_listen_closed(pcb);
TCP_RMV(&pcb->ctx->listen_pcbs, pcb);
tcp_free_listen(pcb);
break;
case SYN_SENT:
TCP_RMV_ACTIVE(pcb->ctx, pcb);
tcp_free(pcb);
break;
default:
return tcp_close_shutdown_fin(pcb);
}
return LERR_OK;
}
static err_t tcp_close_shutdown_fin(struct tcp_pcb *pcb)
{
err_t err = LERR_OK;
if (!pcb) return LERR_ARG;
switch (pcb->state) {
case SYN_RCVD:
err = tcp_send_fin(pcb);
if (err == LERR_OK) pcb->state = FIN_WAIT_1;
break;
case ESTABLISHED:
err = tcp_send_fin(pcb);
if (err == LERR_OK) pcb->state = FIN_WAIT_1;
break;
case CLOSE_WAIT:
err = tcp_send_fin(pcb);
if (err == LERR_OK) pcb->state = LAST_ACK;
break;
default:
return LERR_OK;
}
if (err == LERR_OK) {
tcp_output(pcb);
} else if (err == LERR_MEM) {
tcp_set_flags(pcb, TF_CLOSEPEND);
return LERR_OK;
}
return err;
}
err_t tcp_close(struct tcp_pcb *pcb)
{
if (!pcb) return LERR_ARG;
if (pcb->state != LISTEN) {
tcp_set_flags(pcb, TF_RXCLOSED);
}
return tcp_close_shutdown(pcb, 1);
}
// ============================================
// 8b. tcp_shutdown
// ============================================
err_t tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx)
{
if (!pcb) return LERR_ARG;
if (pcb->state == LISTEN) return LERR_CONN;
if (shut_rx) {
tcp_set_flags(pcb, TF_RXCLOSED);
if (shut_tx) return tcp_close_shutdown(pcb, 1);
if (pcb->refused_data != NULL) {
pbuf_free(pcb->refused_data);
pcb->refused_data = NULL;
}
}
if (shut_tx) {
switch (pcb->state) {
case SYN_RCVD:
case ESTABLISHED:
case CLOSE_WAIT:
return tcp_close_shutdown(pcb, (uint8_t)shut_rx);
default:
return LERR_CONN;
}
}
return LERR_OK;
}
// ============================================
// 9. tcp_abandon / tcp_abort
// ============================================
void tcp_abandon(struct tcp_pcb *pcb, int reset)
{
uint32_t seqno, ackno;
tcp_err_fn errf;
void *errf_arg;
if (!pcb) return;
if (pcb->state == LISTEN) return;
if (pcb->state == TIME_WAIT) {
TCP_RMV(&pcb->ctx->tw_pcbs, pcb);
tcp_free(pcb);
return;
}
int send_rst = 0;
uint16_t local_port = 0;
enum tcp_state last_state;
seqno = pcb->snd_nxt;
ackno = pcb->rcv_nxt;
errf = pcb->errf;
errf_arg = pcb->callback_arg;
if (pcb->state == CLOSED) {
if (pcb->local_port != 0) {
TCP_RMV(&pcb->ctx->bound_pcbs, pcb);
}
} else {
send_rst = reset;
local_port = pcb->local_port;
TCP_RMV_ACTIVE(pcb->ctx, pcb);
}
if (pcb->unacked != NULL) tcp_segs_free_local(pcb->ctx, pcb->unacked);
if (pcb->unsent != NULL) tcp_segs_free_local(pcb->ctx, pcb->unsent);
if (pcb->ooseq != NULL) tcp_segs_free_local(pcb->ctx, pcb->ooseq);
if (send_rst) {
tcp_rst(pcb, seqno, ackno, pcb->local_ip, pcb->remote_ip, local_port, pcb->remote_port);
}
last_state = pcb->state;
tcp_free(pcb);
TCP_EVENT_ERR(last_state, errf, errf_arg, LERR_ABRT);
}
void tcp_abort(struct tcp_pcb *pcb)
{
tcp_abandon(pcb, 1);
}
// ============================================
// 9. tcp_bind
// ============================================
err_t tcp_bind(struct tcp_pcb *pcb, uint32_t ipaddr, uint16_t port)
{
struct tcp_pcb *cpcb;
struct lwip_tcp_ctx *ctx;
if (!pcb) return LERR_ARG;
if (pcb->state != CLOSED) return LERR_VAL;
ctx = pcb->ctx;
uint16_t new_port = port;
if (new_port == 0) {
new_port = tcp_new_port(pcb);
if (new_port == 0) return LERR_BUF;
} else {
struct tcp_pcb *lists[4] = { ctx->listen_pcbs, ctx->bound_pcbs, ctx->active_pcbs, ctx->tw_pcbs };
int i;
for (i = 0; i < 4; i++) {
for (cpcb = lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == new_port) {
if (cpcb->local_ip == 0 || ipaddr == 0 || cpcb->local_ip == ipaddr) {
return LERR_USE;
}
}
}
}
}
if (ipaddr != 0) pcb->local_ip = ipaddr;
pcb->local_port = ntohs(new_port);
TCP_REG(&ctx->bound_pcbs, pcb);
return LERR_OK;
}
// ============================================
// 10. tcp_accept_null / tcp_listen
// ============================================
static err_t tcp_accept_null(void *arg, struct tcp_pcb *pcb, err_t err)
{
(void)arg;
(void)err;
if (pcb) tcp_abort(pcb);
return LERR_ABRT;
}
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
{
struct tcp_pcb_listen *lpcb = NULL;
if (!pcb) return NULL;
if (pcb->state != CLOSED) return NULL;
if (pcb->state == LISTEN) return pcb;
lpcb = (struct tcp_pcb_listen *)memory_pool_alloc(pcb->ctx->pcb_listen_pool);
if (lpcb == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen: out of memory");
return NULL;
}
memset(lpcb, 0, sizeof(*lpcb));
lpcb->next = NULL;
lpcb->ctx = pcb->ctx;
lpcb->callback_arg = pcb->callback_arg;
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN;
lpcb->prio = pcb->prio;
lpcb->local_ip = pcb->local_ip;
lpcb->accept = tcp_accept_null;
if (pcb->local_port != 0) {
TCP_RMV(&pcb->ctx->bound_pcbs, pcb);
}
tcp_free(pcb);
TCP_REG(&lpcb->ctx->listen_pcbs, (struct tcp_pcb *)lpcb);
return (struct tcp_pcb *)lpcb;
}
// ============================================
// 11. tcp_update_rcv_ann_wnd
// ============================================
uint32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb)
{
uint32_t new_right_edge;
if (!pcb) return 0;
new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd;
if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) {
pcb->rcv_ann_wnd = pcb->rcv_wnd;
return new_right_edge - pcb->rcv_ann_right_edge;
} else {
if (TCP_SEQ_GT(pcb->rcv_nxt, pcb->rcv_ann_right_edge)) {
pcb->rcv_ann_wnd = 0;
} else {
uint32_t new_rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt;
pcb->rcv_ann_wnd = (tcpwnd_size_t)new_rcv_ann_wnd;
}
return 0;
}
}
// ============================================
// 12. tcp_recved
// ============================================
void tcp_recved(struct tcp_pcb *pcb, uint16_t len)
{
uint32_t wnd_inflation;
tcpwnd_size_t rcv_wnd;
if (!pcb) return;
if (pcb->state == LISTEN) return;
rcv_wnd = (tcpwnd_size_t)(pcb->rcv_wnd + len);
if ((rcv_wnd > TCP_WND_MAX(pcb)) || (rcv_wnd < pcb->rcv_wnd)) {
pcb->rcv_wnd = TCP_WND_MAX(pcb);
} else {
pcb->rcv_wnd = rcv_wnd;
}
wnd_inflation = tcp_update_rcv_ann_wnd(pcb);
if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) {
tcp_ack_now(pcb);
tcp_output(pcb);
}
}
// ============================================
// 13. tcp_new_port
// ============================================
static uint16_t tcp_new_port(struct tcp_pcb *pcb)
{
struct lwip_tcp_ctx *ctx = pcb->ctx;
uint16_t n = 0;
ctx->port_seed++;
if (ctx->port_seed < TCP_LOCAL_PORT_RANGE_START || ctx->port_seed >= TCP_LOCAL_PORT_RANGE_END) {
ctx->port_seed = TCP_LOCAL_PORT_RANGE_START;
}
again:
struct tcp_pcb *lists[4] = { ctx->listen_pcbs, ctx->bound_pcbs, ctx->active_pcbs, ctx->tw_pcbs };
int i;
for (i = 0; i < 4; i++) {
struct tcp_pcb *cpcb;
for (cpcb = lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == ctx->port_seed) {
n++;
if (n > (TCP_LOCAL_PORT_RANGE_END - TCP_LOCAL_PORT_RANGE_START)) {
return 0;
}
ctx->port_seed++;
if (ctx->port_seed >= TCP_LOCAL_PORT_RANGE_END) {
ctx->port_seed = TCP_LOCAL_PORT_RANGE_START;
}
goto again;
}
}
}
return ctx->port_seed;
}
// ============================================
// 9b. tcp_connect
// ============================================
err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ipaddr, uint16_t port, tcp_connected_fn connected)
{
err_t ret;
uint32_t iss;
uint16_t old_local_port;
if (!pcb) return LERR_ARG;
if (pcb->state != CLOSED) return LERR_ISCONN;
pcb->remote_ip = ipaddr;
pcb->remote_port = ntohs(port);
old_local_port = pcb->local_port;
if (pcb->local_port == 0) {
pcb->local_port = tcp_new_port(pcb);
if (pcb->local_port == 0) return LERR_BUF;
}
iss = tcp_next_iss(pcb);
pcb->rcv_nxt = 0;
pcb->snd_nxt = iss;
pcb->lastack = iss - 1;
pcb->snd_wl2 = iss - 1;
pcb->snd_lbb = iss - 1;
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->rcv_ann_right_edge = pcb->rcv_nxt;
pcb->snd_wnd = TCP_WND;
pcb->mss = INITIAL_MSS;
pcb->cwnd = 1;
pcb->connected = connected;
ret = tcp_enqueue_flags(pcb, TCP_SYN);
if (ret == LERR_OK) {
pcb->state = SYN_SENT;
if (old_local_port != 0) {
TCP_RMV(&pcb->ctx->bound_pcbs, pcb);
}
TCP_REG_ACTIVE(pcb->ctx, pcb);
tcp_output(pcb);
}
return ret;
}
// ============================================
// 14. tcp_slowtmr
// ============================================
void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
{
struct tcp_pcb *pcb, *prev;
tcpwnd_size_t eff_wnd;
uint8_t pcb_remove;
uint8_t pcb_reset;
err_t err;
if (!ctx) return;
ctx->ticks++;
ctx->slowtmr_ctr++;
// ---- process active PCBs ----
prev = NULL;
pcb = ctx->active_pcbs;
while (pcb != NULL) {
if (pcb->last_timer == ctx->slowtmr_ctr) {
prev = pcb;
pcb = pcb->next;
continue;
}
pcb->last_timer = ctx->slowtmr_ctr;
pcb_remove = 0;
pcb_reset = 0;
if (pcb->state == SYN_SENT && pcb->nrtx >= TCP_SYNMAXRTX) {
pcb_remove = 1;
} else if (pcb->nrtx >= TCP_MAXRTX) {
pcb_remove = 1;
} else {
if (pcb->persist_backoff > 0) {
if (pcb->persist_probe >= TCP_MAXRTX) {
pcb_remove = 1;
} else {
uint8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff - 1];
if (pcb->persist_cnt < backoff_cnt) {
pcb->persist_cnt++;
}
if (pcb->persist_cnt >= backoff_cnt) {
int next_slot = 1;
if (pcb->snd_wnd == 0) {
if (tcp_zero_window_probe(pcb) != LERR_OK) next_slot = 0;
} else {
if (tcp_split_unsent_seg(pcb, (uint16_t)pcb->snd_wnd) == LERR_OK) {
if (tcp_output(pcb) == LERR_OK) next_slot = 0;
}
}
if (next_slot) {
pcb->persist_cnt = 0;
if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {
pcb->persist_backoff++;
}
}
}
}
} else {
if ((pcb->rtime >= 0) && (pcb->rtime < 0x7FFF)) ++pcb->rtime;
if (pcb->rtime >= pcb->rto) {
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);
int calc_rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[backoff_idx];
pcb->rto = (int16_t)LWIP_MIN(calc_rto, 0x7FFF);
}
pcb->rtime = 0;
eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
pcb->ssthresh = eff_wnd >> 1;
if (pcb->ssthresh < (tcpwnd_size_t)(pcb->mss << 1)) {
pcb->ssthresh = (tcpwnd_size_t)(pcb->mss << 1);
}
pcb->cwnd = pcb->mss;
pcb->bytes_acked = 0;
tcp_rexmit_rto_commit(pcb);
}
}
}
}
// FIN_WAIT_2 timeout
if (pcb->state == FIN_WAIT_2) {
if (pcb->flags & TF_RXCLOSED) {
if ((uint32_t)(ctx->ticks - pcb->tmr) > TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
pcb_remove = 1;
}
}
}
// OOSEQ timeout
if (pcb->ooseq != NULL &&
(ctx->ticks - pcb->tmr >= (uint32_t)pcb->rto * TCP_OOSEQ_TIMEOUT)) {
tcp_segs_free_local(ctx, pcb->ooseq);
pcb->ooseq = NULL;
}
// SYN_RCVD timeout
if (pcb->state == SYN_RCVD) {
if ((uint32_t)(ctx->ticks - pcb->tmr) > TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
pcb_remove = 1;
}
}
// LAST_ACK timeout
if (pcb->state == LAST_ACK) {
if ((uint32_t)(ctx->ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
pcb_remove = 1;
}
}
if (pcb_remove) {
struct tcp_pcb *pcb2;
tcp_err_fn err_fn = pcb->errf;
void *err_arg;
enum tcp_state last_state;
tcp_pcb_purge(pcb);
if (prev != NULL) {
prev->next = pcb->next;
} else {
ctx->active_pcbs = pcb->next;
}
if (pcb_reset) {
tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, pcb->local_ip, pcb->remote_ip,
pcb->local_port, pcb->remote_port);
}
err_arg = pcb->callback_arg;
last_state = pcb->state;
pcb2 = pcb;
pcb = pcb->next;
tcp_free(pcb2);
TCP_EVENT_ERR(last_state, err_fn, err_arg, LERR_ABRT);
} else {
prev = pcb;
pcb = pcb->next;
prev->polltmr++;
if (prev->polltmr >= prev->pollinterval) {
prev->polltmr = 0;
err = LERR_OK;
TCP_EVENT_POLL(prev, err);
if (err == LERR_OK) {
tcp_output(prev);
}
}
}
}
// ---- process TIME-WAIT PCBs ----
prev = NULL;
pcb = ctx->tw_pcbs;
while (pcb != NULL) {
pcb_remove = 0;
if ((uint32_t)(ctx->ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
pcb_remove = 1;
}
if (pcb_remove) {
struct tcp_pcb *pcb2;
tcp_pcb_purge(pcb);
if (prev != NULL) {
prev->next = pcb->next;
} else {
ctx->tw_pcbs = pcb->next;
}
pcb2 = pcb;
pcb = pcb->next;
tcp_free(pcb2);
} else {
prev = pcb;
pcb = pcb->next;
}
}
}
// ============================================
// 15. tcp_fasttmr
// ============================================
void tcp_fasttmr(struct lwip_tcp_ctx *ctx)
{
struct tcp_pcb *pcb;
if (!ctx) return;
ctx->slowtmr_ctr++;
pcb = ctx->active_pcbs;
while (pcb != NULL) {
if (pcb->last_timer != ctx->slowtmr_ctr) {
struct tcp_pcb *next;
pcb->last_timer = ctx->slowtmr_ctr;
if (pcb->flags & TF_ACK_DELAY) {
tcp_ack_now(pcb);
tcp_output(pcb);
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
}
if (pcb->flags & TF_CLOSEPEND) {
tcp_clear_flags(pcb, TF_CLOSEPEND);
tcp_close_shutdown_fin(pcb);
}
next = pcb->next;
if (pcb->refused_data != NULL) {
tcp_process_refused_data(pcb);
}
pcb = next;
} else {
pcb = pcb->next;
}
}
}
// ============================================
// 16. tcp_txnow
// ============================================
void tcp_txnow(struct lwip_tcp_ctx *ctx)
{
struct tcp_pcb *pcb;
if (!ctx) return;
for (pcb = ctx->active_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->flags & TF_NAGLEMEMERR) {
tcp_output(pcb);
}
}
}
// ============================================
// 17. tcp_process_refused_data
// ============================================
err_t tcp_process_refused_data(struct tcp_pcb *pcb)
{
if (!pcb) return LERR_ARG;
{
err_t err;
uint8_t refused_flags = pcb->refused_data->flags;
struct pbuf *refused_data = pcb->refused_data;
pcb->refused_data = NULL;
TCP_EVENT_RECV(pcb, refused_data, LERR_OK, err);
if (err == LERR_OK) {
if (refused_flags & PBUF_FLAG_TCP_FIN) {
if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) pcb->rcv_wnd++;
TCP_EVENT_CLOSED(pcb, err);
if (err == LERR_ABRT) return LERR_ABRT;
}
} else if (err == LERR_ABRT) {
return LERR_ABRT;
} else {
pcb->refused_data = refused_data;
return LERR_INPROGRESS;
}
}
return LERR_OK;
}
// ============================================
// 18. tcp_setprio
// ============================================
void tcp_setprio(struct tcp_pcb *pcb, uint8_t prio)
{
if (pcb) pcb->prio = prio;
}
// ============================================
// 19. tcp_seg_copy
// ============================================
struct tcp_seg *tcp_seg_copy(struct tcp_seg *seg)
{
struct tcp_seg *cseg;
if (!seg) return NULL;
// ctx must be obtained from caller context — this stub requires
// callers to use a different API (tcp_seg_copy_with_ctx)
// The out.c module provides its own stub; this one is for tcp_in.c callers.
cseg = NULL;
return cseg;
}
struct tcp_seg *tcp_seg_copy_with_ctx(struct lwip_tcp_ctx *ctx, struct tcp_seg *seg)
{
struct tcp_seg *cseg;
if (!seg || !ctx) return NULL;
cseg = (struct tcp_seg *)memory_pool_alloc(ctx->seg_pool);
if (cseg == NULL) return NULL;
memcpy(cseg, seg, sizeof(struct tcp_seg));
pbuf_ref(cseg->p);
return cseg;
}
// ============================================
// 20. tcp_recv_null
// ============================================
err_t tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
(void)arg;
if (!pcb) return LERR_ARG;
if (p != NULL) {
tcp_recved(pcb, p->tot_len);
pbuf_free(p);
} else if (err == LERR_OK) {
return tcp_close(pcb);
}
return LERR_OK;
}
// ============================================
// 21. tcp_kill_prio / tcp_kill_state / tcp_kill_timewait / tcp_handle_closepend
// ============================================
static void tcp_kill_prio(struct lwip_tcp_ctx *ctx, uint8_t prio)
{
struct tcp_pcb *pcb, *inactive;
uint32_t inactivity;
uint8_t mprio;
if (!ctx) return;
mprio = LWIP_MIN(TCP_PRIO_MAX, prio);
if (mprio == 0) return;
mprio--;
inactivity = 0;
inactive = NULL;
for (pcb = ctx->active_pcbs; pcb != NULL; pcb = pcb->next) {
if ((pcb->prio < mprio) ||
((pcb->prio == mprio) && ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity))) {
inactivity = ctx->ticks - pcb->tmr;
inactive = pcb;
mprio = pcb->prio;
}
}
if (inactive != NULL) tcp_abort(inactive);
}
static void tcp_kill_state(struct lwip_tcp_ctx *ctx, enum tcp_state state)
{
struct tcp_pcb *pcb, *inactive;
uint32_t inactivity;
if (!ctx) return;
inactivity = 0;
inactive = NULL;
for (pcb = ctx->active_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->state == state) {
if ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity) {
inactivity = ctx->ticks - pcb->tmr;
inactive = pcb;
}
}
}
if (inactive != NULL) tcp_abandon(inactive, 0);
}
static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx)
{
struct tcp_pcb *pcb, *inactive;
uint32_t inactivity;
if (!ctx) return;
inactivity = 0;
inactive = NULL;
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
if ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity) {
inactivity = ctx->ticks - pcb->tmr;
inactive = pcb;
}
}
if (inactive != NULL) tcp_abort(inactive);
}
static void tcp_handle_closepend(struct lwip_tcp_ctx *ctx)
{
struct tcp_pcb *pcb;
if (!ctx) return;
for (pcb = ctx->active_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->flags & TF_CLOSEPEND) {
tcp_clear_flags(pcb, TF_CLOSEPEND);
tcp_close_shutdown_fin(pcb);
}
}
}
// ============================================
// 22. tcp_alloc / tcp_new
// ============================================
struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
{
struct tcp_pcb *pcb;
if (!ctx) return NULL;
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
if (pcb == NULL) {
tcp_handle_closepend(ctx);
tcp_kill_timewait(ctx);
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
if (pcb == NULL) {
tcp_kill_state(ctx, LAST_ACK);
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
if (pcb == NULL) {
tcp_kill_state(ctx, CLOSING);
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
if (pcb == NULL) {
tcp_kill_prio(ctx, prio);
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
}
}
}
}
if (pcb != NULL) {
memset(pcb, 0, sizeof(struct tcp_pcb));
pcb->prio = prio;
pcb->ctx = ctx;
pcb->snd_buf = TCP_SND_BUF;
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->ttl = 64;
pcb->mss = INITIAL_MSS;
pcb->rto = 3000 / TCP_SLOW_INTERVAL;
pcb->sv = 3000 / TCP_SLOW_INTERVAL;
pcb->rtime = -1;
pcb->cwnd = 1;
pcb->tmr = ctx->ticks;
pcb->last_timer = ctx->slowtmr_ctr;
pcb->ssthresh = TCP_SND_BUF;
pcb->recv = tcp_recv_null;
pcb->keep_idle = TCP_KEEPIDLE_DEFAULT;
}
return pcb;
}
struct tcp_pcb *tcp_new(struct lwip_tcp_ctx *ctx)
{
return tcp_alloc(ctx, TCP_PRIO_NORMAL);
}
// ============================================
// 23. tcp_arg / tcp_recv / tcp_sent / tcp_err / tcp_accept / tcp_poll
// ============================================
void tcp_arg(struct tcp_pcb *pcb, void *arg)
{
if (pcb) pcb->callback_arg = arg;
}
void tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
{
if (pcb && pcb->state != LISTEN) pcb->recv = recv;
}
void tcp_sent(struct tcp_pcb *pcb, tcp_sent_fn sent)
{
if (pcb && pcb->state != LISTEN) pcb->sent = sent;
}
void tcp_err(struct tcp_pcb *pcb, tcp_err_fn errf)
{
if (pcb && pcb->state != LISTEN) pcb->errf = errf;
}
void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
{
if (pcb && pcb->state == LISTEN) {
struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen *)pcb;
lpcb->accept = accept;
}
}
void tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval)
{
if (!pcb || pcb->state == LISTEN) return;
pcb->poll = poll;
pcb->pollinterval = interval;
}
// ============================================
// 24. tcp_pcb_purge / tcp_pcb_remove
// ============================================
void tcp_pcb_purge(struct tcp_pcb *pcb)
{
if (!pcb) return;
if (pcb->state == CLOSED || pcb->state == TIME_WAIT || pcb->state == LISTEN) return;
if (pcb->refused_data != NULL) {
pbuf_free(pcb->refused_data);
pcb->refused_data = NULL;
}
if (pcb->ooseq != NULL) {
tcp_segs_free_local(pcb->ctx, pcb->ooseq);
pcb->ooseq = NULL;
}
pcb->rtime = -1;
tcp_segs_free_local(pcb->ctx, pcb->unsent);
tcp_segs_free_local(pcb->ctx, pcb->unacked);
pcb->unacked = pcb->unsent = NULL;
}
void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb)
{
if (!pcblist || !pcb) return;
TCP_RMV(pcblist, pcb);
tcp_pcb_purge(pcb);
if (pcb->state != TIME_WAIT && pcb->state != LISTEN && (pcb->flags & TF_ACK_DELAY)) {
tcp_ack_now(pcb);
tcp_output(pcb);
}
pcb->state = CLOSED;
pcb->local_port = 0;
}
// ============================================
// 25. tcp_next_iss
// ============================================
uint32_t tcp_next_iss(struct tcp_pcb *pcb)
{
if (!pcb) return 0;
pcb->ctx->iss_seed = (uint16_t)(pcb->ctx->iss_seed + 6510);
return pcb->ctx->iss_seed + pcb->ctx->ticks;
}
// ============================================
// 26. tcp_free_ooseq
// ============================================
void tcp_free_ooseq(struct tcp_pcb *pcb)
{
if (pcb && pcb->ooseq) {
tcp_segs_free_local(pcb->ctx, pcb->ooseq);
pcb->ooseq = NULL;
}
}
// ============================================
// 27. tcp_debug_state_str
// ============================================
const char *tcp_debug_state_str(enum tcp_state s)
{
return tcp_state_str[s];
}
// ============================================
// 28. tcp_tcp_get_tcp_addrinfo
// ============================================
err_t tcp_tcp_get_tcp_addrinfo(struct tcp_pcb *pcb, int local, uint32_t *addr, uint16_t *port)
{
if (!pcb) return LERR_VAL;
if (local) {
if (addr) *addr = pcb->local_ip;
if (port) *port = pcb->local_port;
} else {
if (addr) *addr = pcb->remote_ip;
if (port) *port = pcb->remote_port;
}
return LERR_OK;
}