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.
1005 lines
29 KiB
1005 lines
29 KiB
// lwip_tcp_out.c — adapted lwIP TCP output module for uTun |
|
#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 <string.h> |
|
|
|
// input pcb re-entrancy guard (set during input processing) |
|
static struct tcp_pcb *tcp_input_pcb = NULL; |
|
|
|
// ---- local helpers ---- |
|
|
|
static void pbuf_cat(struct pbuf *h, struct pbuf *t) |
|
{ |
|
if (h == NULL || t == NULL) return; |
|
struct pbuf *p = h; |
|
while (p->next) { p->tot_len = (uint16_t)(p->tot_len + t->tot_len); p = p->next; } |
|
p->tot_len = (uint16_t)(p->tot_len + t->tot_len); |
|
p->next = t; |
|
} |
|
|
|
static void pbuf_realloc(struct pbuf *p, uint16_t new_len) |
|
{ |
|
uint16_t cut; |
|
struct pbuf *q, *prev; |
|
|
|
if (p == NULL || p->tot_len <= new_len) return; |
|
cut = (uint16_t)(p->tot_len - new_len); |
|
|
|
while (cut > 0) { |
|
prev = NULL; |
|
for (q = p; q->next != NULL; q = q->next) prev = q; |
|
if (cut >= q->len) { |
|
cut = (uint16_t)(cut - q->len); |
|
if (prev) { |
|
prev->next = NULL; |
|
} |
|
pbuf_free(q); |
|
if (prev == NULL) break; |
|
} else { |
|
q->len = (uint16_t)(q->len - cut); |
|
cut = 0; |
|
} |
|
} |
|
p->tot_len = new_len; |
|
} |
|
|
|
// segment free helpers (need ctx->seg_pool for memory_pool_free) |
|
static void tcp_seg_free_local(struct tcp_pcb *pcb, struct tcp_seg *seg) |
|
{ |
|
if (seg == NULL) return; |
|
if (seg->p != NULL) pbuf_free(seg->p); |
|
memory_pool_free(pcb->ctx->seg_pool, seg); |
|
} |
|
|
|
static void tcp_segs_free_local(struct tcp_pcb *pcb, struct tcp_seg *seg) |
|
{ |
|
while (seg != NULL) { |
|
struct tcp_seg *next = seg->next; |
|
tcp_seg_free_local(pcb, seg); |
|
seg = next; |
|
} |
|
} |
|
|
|
// LWIP_TCP_OPT_LENGTH: only MSS option is supported |
|
#define LWIP_TCP_OPT_LENGTH(flags) (((flags) & TF_SEG_OPTS_MSS) ? 4 : 0) |
|
#define LWIP_TCP_OPT_LENGTH_SEGMENT(flags, pcb) LWIP_TCP_OPT_LENGTH(flags) |
|
|
|
// TCP data copy — plain memcpy (no checksum-on-copy) |
|
#define TCP_DATA_COPY(dst, src, len, seg) memcpy(dst, src, len) |
|
#define TCP_DATA_COPY2(dst, src, len, c0, c1) memcpy(dst, src, len) |
|
|
|
// tcp_pbuf_prealloc — simplified (no oversize) |
|
#define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length)) |
|
|
|
// Forward |
|
static err_t tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb); |
|
|
|
|
|
// ---- tcp_create_segment ---- |
|
|
|
static struct tcp_seg * |
|
tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, |
|
uint8_t hdrflags, uint32_t seqno, uint8_t optflags) |
|
{ |
|
struct tcp_seg *seg; |
|
uint8_t optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb); |
|
|
|
seg = (struct tcp_seg *)memory_pool_alloc(pcb->ctx->seg_pool); |
|
if (seg == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_create_segment: no memory"); |
|
pbuf_free(p); |
|
return NULL; |
|
} |
|
seg->flags = optflags; |
|
seg->next = NULL; |
|
seg->p = p; |
|
seg->len = (uint16_t)(p->tot_len - optlen); |
|
|
|
if (pbuf_header(p, (int16_t)TCP_HLEN)) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_create_segment: no room for TCP header"); |
|
tcp_seg_free_local((struct tcp_pcb *)pcb, seg); |
|
return NULL; |
|
} |
|
seg->tcphdr = (struct tcp_hdr *)seg->p->payload; |
|
seg->tcphdr->src = htons(pcb->local_port); |
|
seg->tcphdr->dest = htons(pcb->remote_port); |
|
seg->tcphdr->seqno = htonl(seqno); |
|
TCPH_HDRLEN_FLAGS_SET(seg->tcphdr, (5 + optlen / 4), hdrflags); |
|
seg->tcphdr->urgp = 0; |
|
return seg; |
|
} |
|
|
|
|
|
// ---- tcp_write_checks ---- |
|
|
|
static err_t |
|
tcp_write_checks(struct tcp_pcb *pcb, uint16_t len) |
|
{ |
|
if (pcb->state != ESTABLISHED && |
|
pcb->state != CLOSE_WAIT && |
|
pcb->state != SYN_SENT && |
|
pcb->state != SYN_RCVD) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write() called in invalid state"); |
|
return LERR_CONN; |
|
} |
|
if (len == 0) return LERR_OK; |
|
if (len > pcb->snd_buf) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: too much data"); |
|
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"); |
|
tcp_set_flags(pcb, TF_NAGLEMEMERR); |
|
return LERR_MEM; |
|
} |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_write ---- |
|
|
|
err_t |
|
tcp_write(struct tcp_pcb *pcb, const void *arg, uint16_t len, uint8_t apiflags) |
|
{ |
|
struct pbuf *concat_p = NULL; |
|
struct tcp_seg *last_unsent = NULL, *seg = NULL, *prev_seg = NULL, *queue = NULL; |
|
uint16_t pos = 0; |
|
uint16_t queuelen; |
|
uint8_t optlen; |
|
uint8_t optflags = 0; |
|
uint16_t extendlen = 0; |
|
err_t err; |
|
uint16_t mss_local; |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
if (arg == NULL) return LERR_ARG; |
|
|
|
mss_local = pcb->mss; |
|
if (pcb->snd_wnd_max > 0) { |
|
uint16_t half = (uint16_t)(pcb->snd_wnd_max / 2); |
|
if (mss_local > half) mss_local = half; |
|
} |
|
if (mss_local == 0) mss_local = pcb->mss; |
|
|
|
err = tcp_write_checks(pcb, len); |
|
if (err != LERR_OK) return err; |
|
queuelen = pcb->snd_queuelen; |
|
|
|
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb); |
|
|
|
// Phase 1: no oversize, skipped |
|
|
|
// Find tail of unsent |
|
if (pcb->unsent != NULL) { |
|
uint16_t space; |
|
uint16_t unsent_optlen; |
|
|
|
for (last_unsent = pcb->unsent; last_unsent->next != NULL; |
|
last_unsent = last_unsent->next); |
|
|
|
unsent_optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(last_unsent->flags, pcb); |
|
space = (uint16_t)(mss_local - (last_unsent->len + unsent_optlen)); |
|
|
|
// Phase 2: chain a new pbuf to end of last unsent |
|
if ((pos < len) && (space > 0) && (last_unsent->len > 0)) { |
|
uint16_t seglen = space; |
|
if (seglen > (uint16_t)(len - pos)) seglen = (uint16_t)(len - pos); |
|
seg = last_unsent; |
|
|
|
if (apiflags & TCP_WRITE_FLAG_COPY) { |
|
concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, NULL, pcb, apiflags, 1); |
|
if (concat_p == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: could not allocate memory for copy"); |
|
goto memerr; |
|
} |
|
TCP_DATA_COPY2(concat_p->payload, (const uint8_t *)arg + pos, seglen, 0, 0); |
|
queuelen = (uint16_t)(queuelen + pbuf_clen(concat_p)); |
|
} else { |
|
struct pbuf *p_tail; |
|
for (p_tail = last_unsent->p; p_tail->next != NULL; p_tail = p_tail->next); |
|
if (((const uint8_t *)p_tail->payload + p_tail->len) == (const uint8_t *)arg) { |
|
extendlen = seglen; |
|
} else { |
|
concat_p = pbuf_alloc(PBUF_RAW, seglen); |
|
if (concat_p == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: could not allocate memory for zero-copy pbuf"); |
|
goto memerr; |
|
} |
|
if (pbuf_take(concat_p, (const uint8_t *)arg + pos, seglen) != 0) { |
|
pbuf_free(concat_p); |
|
concat_p = NULL; |
|
goto memerr; |
|
} |
|
queuelen = (uint16_t)(queuelen + pbuf_clen(concat_p)); |
|
} |
|
} |
|
pos = (uint16_t)(pos + seglen); |
|
} |
|
} |
|
|
|
// Phase 3: create new segments |
|
while (pos < len) { |
|
struct pbuf *p; |
|
uint16_t left = (uint16_t)(len - pos); |
|
uint16_t max_len = (uint16_t)(mss_local - optlen); |
|
uint16_t seglen = left; |
|
if (seglen > max_len) seglen = max_len; |
|
|
|
if (apiflags & TCP_WRITE_FLAG_COPY) { |
|
p = tcp_pbuf_prealloc(PBUF_TRANSPORT, (uint16_t)(seglen + optlen), mss_local, NULL, pcb, apiflags, (queue == NULL)); |
|
if (p == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: could not allocate memory for pbuf copy"); |
|
goto memerr; |
|
} |
|
memcpy((uint8_t *)p->payload + optlen, (const uint8_t *)arg + pos, seglen); |
|
} else { |
|
struct pbuf *p2; |
|
p2 = pbuf_alloc(PBUF_TRANSPORT, seglen); |
|
if (p2 == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: could not allocate memory for zero-copy pbuf"); |
|
goto memerr; |
|
} |
|
if (pbuf_take(p2, (const uint8_t *)arg + pos, seglen) != 0) { |
|
pbuf_free(p2); |
|
goto memerr; |
|
} |
|
p = pbuf_alloc(PBUF_TRANSPORT, optlen); |
|
if (p == NULL) { |
|
pbuf_free(p2); |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: could not allocate memory for header pbuf"); |
|
goto memerr; |
|
} |
|
pbuf_cat(p, p2); |
|
} |
|
|
|
queuelen = (uint16_t)(queuelen + pbuf_clen(p)); |
|
|
|
if (queuelen > TCP_SND_QUEUELEN) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_write: queue too long"); |
|
pbuf_free(p); |
|
goto memerr; |
|
} |
|
|
|
seg = tcp_create_segment(pcb, p, 0, (uint32_t)(pcb->snd_lbb + pos), optflags); |
|
if (seg == NULL) goto memerr; |
|
|
|
if (queue == NULL) { |
|
queue = seg; |
|
} else { |
|
prev_seg->next = seg; |
|
} |
|
prev_seg = seg; |
|
pos = (uint16_t)(pos + seglen); |
|
} |
|
|
|
// Phase 2 commit: concat to last_unsent |
|
if (concat_p != NULL) { |
|
pbuf_cat(last_unsent->p, concat_p); |
|
last_unsent->len = (uint16_t)(last_unsent->len + concat_p->tot_len); |
|
} else if (extendlen > 0) { |
|
struct pbuf *p; |
|
for (p = last_unsent->p; p->next != NULL; p = p->next) |
|
p->tot_len = (uint16_t)(p->tot_len + extendlen); |
|
p->tot_len = (uint16_t)(p->tot_len + extendlen); |
|
p->len = (uint16_t)(p->len + extendlen); |
|
last_unsent->len = (uint16_t)(last_unsent->len + extendlen); |
|
} |
|
|
|
// Phase 3 commit: append queue to unsent |
|
if (last_unsent == NULL) { |
|
pcb->unsent = queue; |
|
} else { |
|
last_unsent->next = queue; |
|
} |
|
|
|
pcb->snd_lbb = (uint32_t)(pcb->snd_lbb + len); |
|
pcb->snd_buf = (tcpwnd_size_t)(pcb->snd_buf - len); |
|
pcb->snd_queuelen = queuelen; |
|
|
|
if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE) == 0)) { |
|
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH); |
|
} |
|
|
|
return LERR_OK; |
|
|
|
memerr: |
|
tcp_set_flags(pcb, TF_NAGLEMEMERR); |
|
if (concat_p != NULL) pbuf_free(concat_p); |
|
if (queue != NULL) tcp_segs_free_local(pcb, queue); |
|
return LERR_MEM; |
|
} |
|
|
|
|
|
// ---- tcp_split_unsent_seg ---- |
|
|
|
err_t |
|
tcp_split_unsent_seg(struct tcp_pcb *pcb, uint16_t split) |
|
{ |
|
struct tcp_seg *seg = NULL, *useg = NULL; |
|
struct pbuf *p = NULL; |
|
uint8_t optlen; |
|
uint8_t optflags; |
|
uint8_t split_flags; |
|
uint8_t remainder_flags; |
|
uint16_t remainder; |
|
uint16_t offset; |
|
|
|
if (pcb == NULL) return LERR_MEM; |
|
useg = pcb->unsent; |
|
if (useg == NULL) return LERR_MEM; |
|
if (split == 0) return LERR_VAL; |
|
if (useg->len <= split) return LERR_OK; |
|
|
|
optflags = useg->flags; |
|
optlen = LWIP_TCP_OPT_LENGTH(optflags); |
|
remainder = (uint16_t)(useg->len - split); |
|
|
|
p = pbuf_alloc(PBUF_TRANSPORT, (uint16_t)(remainder + optlen)); |
|
if (p == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_split_unsent_seg: could not allocate pbuf"); |
|
goto memerr; |
|
} |
|
|
|
offset = (uint16_t)(useg->p->tot_len - useg->len + split); |
|
if (pbuf_copy_partial(useg->p, (uint8_t *)p->payload + optlen, remainder, offset) != remainder) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_split_unsent_seg: could not copy pbuf remainder"); |
|
goto memerr; |
|
} |
|
|
|
split_flags = TCPH_FLAGS(useg->tcphdr); |
|
remainder_flags = 0; |
|
if (split_flags & TCP_PSH) { split_flags &= (uint8_t)~TCP_PSH; remainder_flags |= TCP_PSH; } |
|
if (split_flags & TCP_FIN) { split_flags &= (uint8_t)~TCP_FIN; remainder_flags |= TCP_FIN; } |
|
|
|
seg = tcp_create_segment(pcb, p, remainder_flags, |
|
ntohl(useg->tcphdr->seqno) + split, optflags); |
|
if (seg == NULL) { |
|
p = NULL; |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_split_unsent_seg: could not create new segment"); |
|
goto memerr; |
|
} |
|
|
|
pcb->snd_queuelen = (uint16_t)(pcb->snd_queuelen - pbuf_clen(useg->p)); |
|
pbuf_realloc(useg->p, (uint16_t)(useg->p->tot_len - remainder)); |
|
useg->len = (uint16_t)(useg->len - remainder); |
|
TCPH_FLAGS_SET(useg->tcphdr, split_flags); |
|
pcb->snd_queuelen = (uint16_t)(pcb->snd_queuelen + pbuf_clen(useg->p)); |
|
pcb->snd_queuelen = (uint16_t)(pcb->snd_queuelen + pbuf_clen(seg->p)); |
|
|
|
seg->next = useg->next; |
|
useg->next = seg; |
|
|
|
return LERR_OK; |
|
|
|
memerr: |
|
if (p != NULL) pbuf_free(p); |
|
return LERR_MEM; |
|
} |
|
|
|
|
|
// ---- tcp_send_fin ---- |
|
|
|
err_t |
|
tcp_send_fin(struct tcp_pcb *pcb) |
|
{ |
|
struct tcp_seg *last_unsent; |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
|
|
if (pcb->unsent != NULL) { |
|
for (last_unsent = pcb->unsent; last_unsent->next != NULL; |
|
last_unsent = last_unsent->next); |
|
if ((TCPH_FLAGS(last_unsent->tcphdr) & (TCP_SYN | TCP_FIN | TCP_RST)) == 0) { |
|
TCPH_SET_FLAG(last_unsent->tcphdr, TCP_FIN); |
|
tcp_set_flags(pcb, TF_FIN); |
|
return LERR_OK; |
|
} |
|
} |
|
return tcp_enqueue_flags(pcb, TCP_FIN); |
|
} |
|
|
|
|
|
// ---- tcp_enqueue_flags ---- |
|
|
|
err_t |
|
tcp_enqueue_flags(struct tcp_pcb *pcb, uint8_t flags) |
|
{ |
|
struct pbuf *p; |
|
struct tcp_seg *seg; |
|
uint8_t optflags = 0; |
|
uint8_t optlen = 0; |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
|
|
if (flags & TCP_SYN) { |
|
optflags = TF_SEG_OPTS_MSS; |
|
} |
|
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb); |
|
|
|
p = pbuf_alloc(PBUF_TRANSPORT, optlen); |
|
if (p == NULL) { |
|
tcp_set_flags(pcb, TF_NAGLEMEMERR); |
|
return LERR_MEM; |
|
} |
|
|
|
seg = tcp_create_segment(pcb, p, flags, pcb->snd_lbb, optflags); |
|
if (seg == NULL) { |
|
tcp_set_flags(pcb, TF_NAGLEMEMERR); |
|
return LERR_MEM; |
|
} |
|
|
|
if (pcb->unsent == NULL) { |
|
pcb->unsent = seg; |
|
} else { |
|
struct tcp_seg *useg; |
|
for (useg = pcb->unsent; useg->next != NULL; useg = useg->next); |
|
useg->next = seg; |
|
} |
|
|
|
if ((flags & TCP_SYN) || (flags & TCP_FIN)) { |
|
pcb->snd_lbb++; |
|
} |
|
if (flags & TCP_FIN) { |
|
tcp_set_flags(pcb, TF_FIN); |
|
} |
|
|
|
pcb->snd_queuelen = (uint16_t)(pcb->snd_queuelen + pbuf_clen(seg->p)); |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_output ---- |
|
|
|
err_t |
|
tcp_output(struct tcp_pcb *pcb) |
|
{ |
|
struct tcp_seg *seg, *useg; |
|
uint32_t wnd, snd_nxt; |
|
err_t err; |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
if (pcb->state == LISTEN) return LERR_OK; |
|
if (pcb->ctx->output == NULL) return LERR_RTE; |
|
|
|
if (tcp_input_pcb == pcb) return LERR_OK; |
|
|
|
wnd = pcb->snd_wnd; |
|
if ((uint32_t)pcb->cwnd < wnd) wnd = pcb->cwnd; |
|
|
|
seg = pcb->unsent; |
|
if (seg == NULL) { |
|
if (pcb->flags & TF_ACK_NOW) return tcp_send_empty_ack(pcb); |
|
goto output_done; |
|
} |
|
|
|
if (pcb->local_ip == 0) return LERR_RTE; |
|
|
|
if ((ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len) > wnd) { |
|
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) { |
|
pcb->persist_cnt = 0; |
|
pcb->persist_backoff = 1; |
|
pcb->persist_probe = 0; |
|
} |
|
if (pcb->flags & TF_ACK_NOW) return tcp_send_empty_ack(pcb); |
|
goto output_done; |
|
} |
|
pcb->persist_backoff = 0; |
|
|
|
useg = pcb->unacked; |
|
if (useg != NULL) { |
|
for (; useg->next != NULL; useg = useg->next); |
|
} |
|
|
|
while (seg != NULL && |
|
(ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len) <= wnd) { |
|
if ((tcp_do_output_nagle(pcb) == 0) && |
|
((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) break; |
|
|
|
if (pcb->state != SYN_SENT) { |
|
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK); |
|
} |
|
|
|
err = tcp_output_segment(seg, pcb); |
|
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) { |
|
tcp_set_flags(pcb, TF_NAGLEMEMERR); |
|
return err; |
|
} |
|
|
|
pcb->unsent = seg->next; |
|
if (pcb->state != SYN_SENT) { |
|
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW); |
|
} |
|
snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg); |
|
if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) pcb->snd_nxt = snd_nxt; |
|
|
|
if (TCP_TCPLEN(seg) > 0) { |
|
seg->next = NULL; |
|
if (pcb->unacked == NULL) { |
|
pcb->unacked = seg; |
|
useg = seg; |
|
} else { |
|
if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))) { |
|
struct tcp_seg **cur_seg = &(pcb->unacked); |
|
while (*cur_seg && |
|
TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) |
|
cur_seg = &((*cur_seg)->next); |
|
seg->next = (*cur_seg); |
|
(*cur_seg) = seg; |
|
} else { |
|
useg->next = seg; |
|
useg = useg->next; |
|
} |
|
} |
|
} else { |
|
tcp_seg_free_local(pcb, seg); |
|
} |
|
seg = pcb->unsent; |
|
} |
|
|
|
output_done: |
|
tcp_clear_flags(pcb, TF_NAGLEMEMERR); |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_output_segment_busy ---- |
|
|
|
static int |
|
tcp_output_segment_busy(const struct tcp_seg *seg) |
|
{ |
|
if (seg == NULL) return 0; |
|
if (seg->p->ref != 1) return 1; |
|
return 0; |
|
} |
|
|
|
|
|
// ---- tcp_ip_checksum ---- |
|
// Checksum for IP header (16-bit one's complement sum over the 20-byte header) |
|
uint16_t |
|
tcp_ip_checksum(const struct ip_hdr *iph) |
|
{ |
|
uint32_t sum = 0; |
|
const uint8_t *b = (const uint8_t *)iph; |
|
uint16_t i; |
|
for (i = 0; i < 20; i += 2) |
|
sum += (uint32_t)((uint16_t)(b[i] << 8) | b[i + 1]); |
|
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); |
|
return (uint16_t)~sum; |
|
} |
|
|
|
|
|
// Checksum for TCP pseudo-header: src_ip, dst_ip, proto, TCP segment length, TCP data |
|
uint16_t |
|
tcp_checksum(const void *data, uint16_t len) |
|
{ |
|
uint32_t sum = 0; |
|
const uint8_t *b = (const uint8_t *)data; |
|
uint16_t i; |
|
for (i = 0; i + 1 < len; i += 2) |
|
sum += (uint32_t)((uint16_t)(b[i] << 8) | b[i + 1]); |
|
if (len & 1) sum += (uint32_t)((uint16_t)b[len - 1] << 8); |
|
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); |
|
return (uint16_t)~sum; |
|
} |
|
|
|
|
|
static uint16_t |
|
tcp_pseudo_checksum(uint32_t src_ip, uint32_t dst_ip, uint8_t proto, uint16_t tcp_len, const struct pbuf *p) |
|
{ |
|
uint32_t sum = 0; |
|
|
|
sum += (uint32_t)ntohs((uint16_t)((src_ip >> 16) & 0xFFFF)); |
|
sum += (uint32_t)ntohs((uint16_t)(src_ip & 0xFFFF)); |
|
sum += (uint32_t)ntohs((uint16_t)((dst_ip >> 16) & 0xFFFF)); |
|
sum += (uint32_t)ntohs((uint16_t)(dst_ip & 0xFFFF)); |
|
sum += (uint32_t)(uint16_t)proto; |
|
sum += (uint32_t)tcp_len; |
|
|
|
const struct pbuf *q; |
|
for (q = p; q != NULL; q = q->next) { |
|
const uint8_t *b = (const uint8_t *)q->payload; |
|
uint16_t remaining = q->len; |
|
uint16_t i; |
|
for (i = 0; i + 1 < remaining; i += 2) |
|
sum += (uint32_t)((uint16_t)(b[i] << 8) | b[i + 1]); |
|
if (remaining & 1) sum += (uint32_t)((uint16_t)b[remaining - 1] << 8); |
|
} |
|
|
|
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); |
|
return (uint16_t)~sum; |
|
} |
|
|
|
|
|
// ---- tcp_output_segment ---- |
|
|
|
static err_t |
|
tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb) |
|
{ |
|
err_t err; |
|
uint16_t len; |
|
uint32_t *opts; |
|
struct ip_hdr *iph; |
|
|
|
if (seg == NULL || pcb == NULL) return LERR_ARG; |
|
|
|
if (tcp_output_segment_busy(seg)) return LERR_OK; |
|
|
|
seg->tcphdr->ackno = htonl(pcb->rcv_nxt); |
|
seg->tcphdr->wnd = htons((uint16_t)pcb->rcv_ann_wnd); |
|
pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd; |
|
|
|
opts = (uint32_t *)(void *)(seg->tcphdr + 1); |
|
if (seg->flags & TF_SEG_OPTS_MSS) { |
|
*opts = TCP_BUILD_MSS_OPTION(TCP_MSS); |
|
opts += 1; |
|
} |
|
|
|
if (pcb->rtime < 0) pcb->rtime = 0; |
|
|
|
if (pcb->rttest == 0) { |
|
pcb->rttest = pcb->ctx->ticks; |
|
pcb->rtseq = ntohl(seg->tcphdr->seqno); |
|
} |
|
|
|
len = (uint16_t)((uint8_t *)seg->tcphdr - (uint8_t *)seg->p->payload); |
|
seg->p->len = (uint16_t)(seg->p->len - len); |
|
seg->p->tot_len = (uint16_t)(seg->p->tot_len - len); |
|
seg->p->payload = seg->tcphdr; |
|
|
|
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)); |
|
|
|
if (pbuf_header(seg->p, (int16_t)sizeof(struct ip_hdr)) == 0) { |
|
iph = (struct ip_hdr *)seg->p->payload; |
|
memset(iph, 0, sizeof(*iph)); |
|
iph->vhl = 0x45; |
|
iph->len = htons(seg->p->tot_len); |
|
iph->id = htons(pcb->ctx->ip_id); |
|
pcb->ctx->ip_id = (uint16_t)(pcb->ctx->ip_id + 1); |
|
iph->ttl = pcb->ttl ? pcb->ttl : 64; |
|
iph->proto = IP_PROTO_TCP; |
|
iph->src = pcb->local_ip; |
|
iph->dst = pcb->remote_ip; |
|
iph->chksum = htons(tcp_ip_checksum(iph)); |
|
err = pcb->ctx->output(pcb->ctx->output_arg, seg->p, pcb->local_ip, pcb->remote_ip); |
|
pbuf_header(seg->p, -(int16_t)sizeof(struct ip_hdr)); |
|
} else { |
|
err = LERR_MEM; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
|
|
// ---- tcp_rexmit_rto_prepare ---- |
|
|
|
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; |
|
|
|
for (seg = pcb->unacked; seg->next != NULL; seg = seg->next) { |
|
if (tcp_output_segment_busy(seg)) return LERR_VAL; |
|
} |
|
if (tcp_output_segment_busy(seg)) return LERR_VAL; |
|
|
|
seg->next = pcb->unsent; |
|
pcb->unsent = pcb->unacked; |
|
pcb->unacked = NULL; |
|
|
|
tcp_set_flags(pcb, TF_RTO); |
|
pcb->rto_end = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg); |
|
pcb->rttest = 0; |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_rexmit_rto_commit ---- |
|
|
|
void |
|
tcp_rexmit_rto_commit(struct tcp_pcb *pcb) |
|
{ |
|
if (pcb == NULL) return; |
|
if (pcb->nrtx < 0xFF) pcb->nrtx++; |
|
tcp_output(pcb); |
|
} |
|
|
|
|
|
// ---- tcp_rexmit_rto ---- |
|
|
|
void |
|
tcp_rexmit_rto(struct tcp_pcb *pcb) |
|
{ |
|
if (pcb == NULL) return; |
|
if (tcp_rexmit_rto_prepare(pcb) == LERR_OK) tcp_rexmit_rto_commit(pcb); |
|
} |
|
|
|
|
|
// ---- tcp_rexmit ---- |
|
|
|
err_t |
|
tcp_rexmit(struct tcp_pcb *pcb) |
|
{ |
|
struct tcp_seg *seg; |
|
struct tcp_seg **cur_seg; |
|
|
|
if (pcb == NULL) return LERR_VAL; |
|
if (pcb->unacked == NULL) return LERR_VAL; |
|
|
|
seg = pcb->unacked; |
|
if (tcp_output_segment_busy(seg)) return LERR_VAL; |
|
|
|
pcb->unacked = seg->next; |
|
cur_seg = &(pcb->unsent); |
|
while (*cur_seg && |
|
TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) |
|
cur_seg = &((*cur_seg)->next); |
|
seg->next = *cur_seg; |
|
*cur_seg = seg; |
|
|
|
if (pcb->nrtx < 0xFF) pcb->nrtx++; |
|
pcb->rttest = 0; |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_rexmit_fast ---- |
|
|
|
void |
|
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) { |
|
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); |
|
if (pcb->ssthresh < 2 * pcb->mss) pcb->ssthresh = (tcpwnd_size_t)(2 * pcb->mss); |
|
|
|
pcb->cwnd = (tcpwnd_size_t)(pcb->ssthresh + 3 * (uint32_t)pcb->mss); |
|
tcp_set_flags(pcb, TF_INFR); |
|
pcb->rtime = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
// ---- tcp_output_alloc_header_common ---- |
|
|
|
static struct pbuf * |
|
tcp_output_alloc_header_common(uint32_t ackno, uint16_t optlen, uint16_t datalen, |
|
uint32_t seqno_be, uint16_t src_port, uint16_t dst_port, |
|
uint8_t flags, uint16_t wnd) |
|
{ |
|
struct tcp_hdr *tcphdr; |
|
struct pbuf *p; |
|
|
|
p = pbuf_alloc(PBUF_IP, (uint16_t)(TCP_HLEN + optlen + datalen)); |
|
if (p != NULL) { |
|
tcphdr = (struct tcp_hdr *)p->payload; |
|
tcphdr->src = htons(src_port); |
|
tcphdr->dest = htons(dst_port); |
|
tcphdr->seqno = seqno_be; |
|
tcphdr->ackno = htonl(ackno); |
|
TCPH_HDRLEN_FLAGS_SET(tcphdr, (5 + optlen / 4), flags); |
|
tcphdr->wnd = htons(wnd); |
|
tcphdr->chksum = 0; |
|
tcphdr->urgp = 0; |
|
} |
|
return p; |
|
} |
|
|
|
|
|
// ---- tcp_output_alloc_header ---- |
|
|
|
static struct pbuf * |
|
tcp_output_alloc_header(struct tcp_pcb *pcb, uint16_t optlen, uint16_t datalen, |
|
uint32_t seqno_be) |
|
{ |
|
struct pbuf *p; |
|
if (pcb == NULL) return NULL; |
|
|
|
p = tcp_output_alloc_header_common(pcb->rcv_nxt, optlen, datalen, |
|
seqno_be, pcb->local_port, pcb->remote_port, TCP_ACK, |
|
(uint16_t)pcb->rcv_ann_wnd); |
|
if (p != NULL) { |
|
pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd; |
|
} |
|
return p; |
|
} |
|
|
|
|
|
// ---- tcp_output_fill_options (simplified — no timestamps, no SACK) ---- |
|
|
|
static void |
|
tcp_output_fill_options(const struct tcp_pcb *pcb, struct pbuf *p, |
|
uint8_t optflags, uint8_t num_sacks) |
|
{ |
|
struct tcp_hdr *tcphdr; |
|
uint32_t *opts; |
|
|
|
(void)pcb; |
|
(void)num_sacks; |
|
if (p == NULL) return; |
|
|
|
tcphdr = (struct tcp_hdr *)p->payload; |
|
opts = (uint32_t *)(void *)(tcphdr + 1); |
|
// no timestamp, no SACK, no WND_SCALE in non-SYN segments |
|
(void)opts; |
|
(void)optflags; |
|
} |
|
|
|
|
|
// ---- tcp_output_control_segment ---- |
|
|
|
static err_t |
|
tcp_output_control_segment(const struct tcp_pcb *pcb, struct pbuf *p, |
|
uint32_t src_ip, uint32_t dst_ip) |
|
{ |
|
err_t err; |
|
struct ip_hdr *iph; |
|
|
|
if (p == NULL) return LERR_OK; |
|
|
|
{ |
|
struct tcp_hdr *tcphdr = (struct tcp_hdr *)p->payload; |
|
tcphdr->chksum = htons(tcp_pseudo_checksum(src_ip, dst_ip, |
|
IP_PROTO_TCP, p->tot_len, p)); |
|
} |
|
|
|
if (pbuf_header(p, (int16_t)sizeof(struct ip_hdr)) == 0) { |
|
iph = (struct ip_hdr *)p->payload; |
|
memset(iph, 0, sizeof(*iph)); |
|
iph->vhl = 0x45; |
|
iph->len = htons(p->tot_len); |
|
if (pcb != NULL) { |
|
iph->id = htons(pcb->ctx->ip_id); |
|
pcb->ctx->ip_id = (uint16_t)(pcb->ctx->ip_id + 1); |
|
iph->ttl = pcb->ttl ? pcb->ttl : 64; |
|
iph->proto = IP_PROTO_TCP; |
|
} else { |
|
iph->id = 0; |
|
iph->ttl = 64; |
|
iph->proto = IP_PROTO_TCP; |
|
} |
|
iph->src = src_ip; |
|
iph->dst = dst_ip; |
|
iph->chksum = htons(tcp_ip_checksum(iph)); |
|
if (pcb != NULL) { |
|
err = pcb->ctx->output(pcb->ctx->output_arg, p, src_ip, dst_ip); |
|
} else { |
|
err = LERR_OK; |
|
} |
|
pbuf_header(p, -(int16_t)sizeof(struct ip_hdr)); |
|
} else { |
|
err = LERR_MEM; |
|
} |
|
|
|
pbuf_free(p); |
|
return err; |
|
} |
|
|
|
|
|
// ---- tcp_rst ---- |
|
|
|
void |
|
tcp_rst(struct tcp_pcb *pcb, uint32_t seqno, uint32_t ackno, |
|
uint32_t local_ip, uint32_t remote_ip, |
|
uint16_t local_port, uint16_t remote_port) |
|
{ |
|
struct pbuf *p; |
|
uint16_t wnd; |
|
uint8_t optlen; |
|
|
|
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb); |
|
wnd = htons(TCP_WND); |
|
|
|
p = tcp_output_alloc_header_common(ackno, optlen, 0, htonl(seqno), |
|
local_port, remote_port, TCP_RST | TCP_ACK, wnd); |
|
if (p == NULL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_rst: could not allocate memory for pbuf"); |
|
return; |
|
} |
|
tcp_output_fill_options(pcb, p, 0, 0); |
|
tcp_output_control_segment(pcb, p, local_ip, remote_ip); |
|
} |
|
|
|
|
|
// ---- tcp_send_empty_ack ---- |
|
|
|
err_t |
|
tcp_send_empty_ack(struct tcp_pcb *pcb) |
|
{ |
|
err_t err; |
|
struct pbuf *p; |
|
uint8_t optlen, optflags = 0; |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
|
|
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb); |
|
|
|
p = tcp_output_alloc_header(pcb, optlen, 0, htonl(pcb->snd_nxt)); |
|
if (p == NULL) { |
|
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; |
|
} |
|
tcp_output_fill_options(pcb, p, optflags, 0); |
|
|
|
err = tcp_output_control_segment(pcb, p, pcb->local_ip, pcb->remote_ip); |
|
if (err != LERR_OK) { |
|
tcp_set_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW); |
|
} else { |
|
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW); |
|
} |
|
return err; |
|
} |
|
|
|
|
|
// ---- tcp_keepalive (stub) ---- |
|
|
|
err_t |
|
tcp_keepalive(struct tcp_pcb *pcb) |
|
{ |
|
(void)pcb; |
|
return LERR_OK; |
|
} |
|
|
|
|
|
// ---- tcp_zero_window_probe ---- |
|
|
|
err_t |
|
tcp_zero_window_probe(struct tcp_pcb *pcb) |
|
{ |
|
err_t err; |
|
struct pbuf *p; |
|
struct tcp_hdr *tcphdr; |
|
struct tcp_seg *seg; |
|
uint16_t len; |
|
uint8_t is_fin; |
|
uint32_t snd_nxt; |
|
uint8_t optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb); |
|
|
|
if (pcb == NULL) return LERR_ARG; |
|
|
|
seg = pcb->unsent; |
|
if (seg == NULL) return LERR_OK; |
|
|
|
if (pcb->persist_probe < 0xFF) pcb->persist_probe++; |
|
|
|
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) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_zero_window_probe: no memory for pbuf"); |
|
return LERR_MEM; |
|
} |
|
tcphdr = (struct tcp_hdr *)p->payload; |
|
|
|
if (is_fin) { |
|
TCPH_FLAGS_SET(tcphdr, TCP_ACK | TCP_FIN); |
|
} else { |
|
char *d = ((char *)p->payload + TCP_HLEN); |
|
pbuf_copy_partial(seg->p, d, 1, (uint16_t)(seg->p->tot_len - seg->len)); |
|
} |
|
|
|
snd_nxt = ntohl(seg->tcphdr->seqno) + 1; |
|
if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) pcb->snd_nxt = snd_nxt; |
|
tcp_output_fill_options(pcb, p, 0, 0); |
|
|
|
err = tcp_output_control_segment(pcb, p, pcb->local_ip, pcb->remote_ip); |
|
return err; |
|
}
|
|
|