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
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; |
|
}
|
|
|