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

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