#include "lwip/opt.h"
#if LWIP_TCP
#include "lwip/priv/tcp_priv.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
#include "lwip/inet_chksum.h"
#include "lwip/stats.h"
#include "lwip/ip6.h"
#include "lwip/ip6_addr.h"
#if LWIP_TCP_TIMESTAMPS
#include "lwip/sys.h"
#endif
#include <string.h>
#ifdef LWIP_HOOK_FILENAME
#include LWIP_HOOK_FILENAME
#endif
#ifdef LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH
#define LWIP_TCP_OPT_LENGTH_SEGMENT(flags, pcb) LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH(pcb, LWIP_TCP_OPT_LENGTH(flags))
#else
#define LWIP_TCP_OPT_LENGTH_SEGMENT(flags, pcb) LWIP_TCP_OPT_LENGTH(flags)
#endif
#if TCP_CHECKSUM_ON_COPY
#define TCP_DATA_COPY(dst, src, len, seg) do { \
tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), \
len, &seg->chksum, &seg->chksum_swapped); \
seg->flags |= TF_SEG_DATA_CHECKSUMMED; } while(0)
#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped) \
tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), len, chksum, chksum_swapped);
#else
#define TCP_DATA_COPY(dst, src, len, seg) MEMCPY(dst, src, len)
#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped) MEMCPY(dst, src, len)
#endif
#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK
#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK 0
#endif
#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL
#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(msg) LWIP_DEBUGF(TCP_DEBUG | LWIP_DBG_LEVEL_WARNING, msg)
#endif
#endif
#if TCP_OVERSIZE
#ifndef TCP_OVERSIZE_CALC_LENGTH
#define TCP_OVERSIZE_CALC_LENGTH(length) ((length) + TCP_OVERSIZE)
#endif
#endif
static err_t tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb, struct netif *netif);
static err_t tcp_output_control_segment_netif(const struct tcp_pcb *pcb, struct pbuf *p,
const ip_addr_t *src, const ip_addr_t *dst,
struct netif *netif);
static struct netif *
tcp_route(const struct tcp_pcb *pcb, const ip_addr_t *src, const ip_addr_t *dst)
{
LWIP_UNUSED_ARG(src);
if ((pcb != NULL) && (pcb->netif_idx != NETIF_NO_INDEX)) {
return netif_get_by_index(pcb->netif_idx);
} else {
return ip_route(src, dst);
}
}
static struct tcp_seg *
tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, u8_t hdrflags, u32_t seqno, u8_t optflags)
{
struct tcp_seg *seg;
u8_t optlen;
LWIP_ASSERT("tcp_create_segment: invalid pcb", pcb != NULL);
LWIP_ASSERT("tcp_create_segment: invalid pbuf", p != NULL);
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb);
if ((seg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_create_segment: no memory.\n"));
pbuf_free(p);
return NULL;
}
seg->flags = optflags;
seg->next = NULL;
seg->p = p;
LWIP_ASSERT("p->tot_len >= optlen", p->tot_len >= optlen);
seg->len = p->tot_len - optlen;
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = 0;
#endif
#if TCP_CHECKSUM_ON_COPY
seg->chksum = 0;
seg->chksum_swapped = 0;
LWIP_ASSERT("invalid optflags passed: TF_SEG_DATA_CHECKSUMMED",
(optflags & TF_SEG_DATA_CHECKSUMMED) == 0);
#endif
if (pbuf_add_header(p, TCP_HLEN)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_create_segment: no room for TCP header in pbuf.\n"));
TCP_STATS_INC(tcp.err);
tcp_seg_free(seg);
return NULL;
}
seg->tcphdr = (struct tcp_hdr *)seg->p->payload;
seg->tcphdr->src = lwip_htons(pcb->local_port);
seg->tcphdr->dest = lwip_htons(pcb->remote_port);
seg->tcphdr->seqno = lwip_htonl(seqno);
TCPH_HDRLEN_FLAGS_SET(seg->tcphdr, (5 + optlen / 4), hdrflags);
seg->tcphdr->urgp = 0;
return seg;
}
#if TCP_OVERSIZE
static struct pbuf *
tcp_pbuf_prealloc(pbuf_layer layer, u16_t length, u16_t max_length,
u16_t *oversize, const struct tcp_pcb *pcb, u8_t apiflags,
u8_t first_seg)
{
struct pbuf *p;
u16_t alloc = length;
LWIP_ASSERT("tcp_pbuf_prealloc: invalid oversize", oversize != NULL);
LWIP_ASSERT("tcp_pbuf_prealloc: invalid pcb", pcb != NULL);
#if LWIP_NETIF_TX_SINGLE_PBUF
LWIP_UNUSED_ARG(max_length);
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(apiflags);
LWIP_UNUSED_ARG(first_seg);
alloc = max_length;
#else
if (length < max_length) {
if ((apiflags & TCP_WRITE_FLAG_MORE) ||
(!(pcb->flags & TF_NODELAY) &&
(!first_seg ||
pcb->unsent != NULL ||
pcb->unacked != NULL))) {
alloc = LWIP_MIN(max_length, LWIP_MEM_ALIGN_SIZE(TCP_OVERSIZE_CALC_LENGTH(length)));
}
}
#endif
p = pbuf_alloc(layer, alloc, PBUF_RAM);
if (p == NULL) {
return NULL;
}
LWIP_ASSERT("need unchained pbuf", p->next == NULL);
*oversize = p->len - length;
p->len = p->tot_len = length;
return p;
}
#else
#define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length), PBUF_RAM)
#endif
#if TCP_CHECKSUM_ON_COPY
static void
tcp_seg_add_chksum(u16_t chksum, u16_t len, u16_t *seg_chksum,
u8_t *seg_chksum_swapped)
{
u32_t helper;
helper = chksum + *seg_chksum;
chksum = FOLD_U32T(helper);
if ((len & 1) != 0) {
*seg_chksum_swapped = 1 - *seg_chksum_swapped;
chksum = SWAP_BYTES_IN_WORD(chksum);
}
*seg_chksum = chksum;
}
#endif
static err_t
tcp_write_checks(struct tcp_pcb *pcb, u16_t len)
{
LWIP_ASSERT("tcp_write_checks: invalid pcb", pcb != NULL);
if ((pcb->state != ESTABLISHED) &&
(pcb->state != CLOSE_WAIT) &&
(pcb->state != SYN_SENT) &&
(pcb->state != SYN_RCVD)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_STATE | LWIP_DBG_LEVEL_SEVERE, ("tcp_write() called in invalid state\n"));
return ERR_CONN;
} else if (len == 0) {
return ERR_OK;
}
if (len > pcb->snd_buf) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too much data (len=%"U16_F" > snd_buf=%"TCPWNDSIZE_F")\n",
len, pcb->snd_buf));
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return ERR_MEM;
}
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: queuelen: %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
if (pcb->snd_queuelen >= LWIP_MIN(TCP_SND_QUEUELEN, (TCP_SNDQUEUELEN_OVERFLOW + 1))) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n",
pcb->snd_queuelen, (u16_t)TCP_SND_QUEUELEN));
TCP_STATS_INC(tcp.memerr);
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return ERR_MEM;
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: pbufs on queue => at least one queue non-empty",
pcb->unacked != NULL || pcb->unsent != NULL);
} else {
LWIP_ASSERT("tcp_write: no pbufs on queue => both queues empty",
pcb->unacked == NULL && pcb->unsent == NULL);
}
return ERR_OK;
}
err_t
tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags)
{
struct pbuf *concat_p = NULL;
struct tcp_seg *last_unsent = NULL, *seg = NULL, *prev_seg = NULL, *queue = NULL;
u16_t pos = 0;
u16_t queuelen;
u8_t optlen;
u8_t optflags = 0;
#if TCP_OVERSIZE
u16_t oversize = 0;
u16_t oversize_used = 0;
#if TCP_OVERSIZE_DBGCHECK
u16_t oversize_add = 0;
#endif
#endif
u16_t extendlen = 0;
#if TCP_CHECKSUM_ON_COPY
u16_t concat_chksum = 0;
u8_t concat_chksum_swapped = 0;
u16_t concat_chksummed = 0;
#endif
err_t err;
u16_t mss_local;
LWIP_ERROR("tcp_write: invalid pcb", pcb != NULL, return ERR_ARG);
mss_local = LWIP_MIN(pcb->mss, TCPWND_MIN16(pcb->snd_wnd_max / 2));
mss_local = mss_local ? mss_local : pcb->mss;
LWIP_ASSERT_CORE_LOCKED();
#if LWIP_NETIF_TX_SINGLE_PBUF
apiflags |= TCP_WRITE_FLAG_COPY;
#endif
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_write(pcb=%p, data=%p, len=%"U16_F", apiflags=%"U16_F")\n",
(void *)pcb, arg, len, (u16_t)apiflags));
LWIP_ERROR("tcp_write: arg == NULL (programmer violates API)",
arg != NULL, return ERR_ARG;);
err = tcp_write_checks(pcb, len);
if (err != ERR_OK) {
return err;
}
queuelen = pcb->snd_queuelen;
#if LWIP_TCP_TIMESTAMPS
if ((pcb->flags & TF_TIMESTAMP)) {
optflags = TF_SEG_OPTS_TS;
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(TF_SEG_OPTS_TS, pcb);
mss_local = LWIP_MAX(mss_local, LWIP_TCP_OPT_LEN_TS + 1);
} else
#endif
{
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb);
}
if (pcb->unsent != NULL) {
u16_t space;
u16_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);
LWIP_ASSERT("mss_local is too small", mss_local >= last_unsent->len + unsent_optlen);
space = mss_local - (last_unsent->len + unsent_optlen);
#if TCP_OVERSIZE
#if TCP_OVERSIZE_DBGCHECK
LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
pcb->unsent_oversize == last_unsent->oversize_left);
#endif
oversize = pcb->unsent_oversize;
if (oversize > 0) {
LWIP_ASSERT("inconsistent oversize vs. space", oversize <= space);
seg = last_unsent;
oversize_used = LWIP_MIN(space, LWIP_MIN(oversize, len));
pos += oversize_used;
oversize -= oversize_used;
space -= oversize_used;
}
LWIP_ASSERT("inconsistent oversize vs. len", (oversize == 0) || (pos == len));
#endif
#if !LWIP_NETIF_TX_SINGLE_PBUF
if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
u16_t seglen = LWIP_MIN(space, len - pos);
seg = last_unsent;
if (apiflags & TCP_WRITE_FLAG_COPY) {
if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
seglen));
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
oversize_add = oversize;
#endif
TCP_DATA_COPY2(concat_p->payload, (const u8_t *)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
#if TCP_CHECKSUM_ON_COPY
concat_chksummed += seglen;
#endif
queuelen += pbuf_clen(concat_p);
} else {
struct pbuf *p;
for (p = last_unsent->p; p->next != NULL; p = p->next);
if (((p->type_internal & (PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_DATA_VOLATILE)) == 0) &&
(const u8_t *)p->payload + p->len == (const u8_t *)arg) {
LWIP_ASSERT("tcp_write: ROM pbufs cannot be oversized", pos == 0);
extendlen = seglen;
} else {
if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
((struct pbuf_rom *)concat_p)->payload = (const u8_t *)arg + pos;
queuelen += pbuf_clen(concat_p);
}
#if TCP_CHECKSUM_ON_COPY
tcp_seg_add_chksum(~inet_chksum((const u8_t *)arg + pos, seglen), seglen,
&concat_chksum, &concat_chksum_swapped);
concat_chksummed += seglen;
#endif
}
pos += seglen;
}
#endif
} else {
#if TCP_OVERSIZE
LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
pcb->unsent_oversize == 0);
#endif
}
while (pos < len) {
struct pbuf *p;
u16_t left = len - pos;
u16_t max_len = mss_local - optlen;
u16_t seglen = LWIP_MIN(left, max_len);
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
#endif
if (apiflags & TCP_WRITE_FLAG_COPY) {
if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, mss_local, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
goto memerr;
}
LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
(p->len >= seglen));
TCP_DATA_COPY2((char *)p->payload + optlen, (const u8_t *)arg + pos, seglen, &chksum, &chksum_swapped);
} else {
struct pbuf *p2;
#if TCP_OVERSIZE
LWIP_ASSERT("oversize == 0", oversize == 0);
#endif
if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
chksum = ~inet_chksum((const u8_t *)arg + pos, seglen);
if (seglen & 1) {
chksum_swapped = 1;
chksum = SWAP_BYTES_IN_WORD(chksum);
}
#endif
((struct pbuf_rom *)p2)->payload = (const u8_t *)arg + pos;
if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
pbuf_free(p2);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for header pbuf\n"));
goto memerr;
}
pbuf_cat(p, p2);
}
queuelen += pbuf_clen(p);
if (queuelen > LWIP_MIN(TCP_SND_QUEUELEN, TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: queue too long %"U16_F" (%d)\n",
queuelen, (int)TCP_SND_QUEUELEN));
pbuf_free(p);
goto memerr;
}
if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = oversize;
#endif
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif
if (queue == NULL) {
queue = seg;
} else {
LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
prev_seg->next = seg;
}
prev_seg = seg;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
lwip_ntohl(seg->tcphdr->seqno),
lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
pos += seglen;
}
#if TCP_OVERSIZE_DBGCHECK
if ((last_unsent != NULL) && (oversize_add != 0)) {
last_unsent->oversize_left += oversize_add;
}
#endif
#if TCP_OVERSIZE
if (oversize_used > 0) {
struct pbuf *p;
for (p = last_unsent->p; p; p = p->next) {
p->tot_len += oversize_used;
if (p->next == NULL) {
TCP_DATA_COPY((char *)p->payload + p->len, arg, oversize_used, last_unsent);
p->len += oversize_used;
}
}
last_unsent->len += oversize_used;
#if TCP_OVERSIZE_DBGCHECK
LWIP_ASSERT("last_unsent->oversize_left >= oversize_used",
last_unsent->oversize_left >= oversize_used);
last_unsent->oversize_left -= oversize_used;
#endif
}
pcb->unsent_oversize = oversize;
#endif
if (concat_p != NULL) {
LWIP_ASSERT("tcp_write: cannot concatenate when pcb->unsent is empty",
(last_unsent != NULL));
pbuf_cat(last_unsent->p, concat_p);
last_unsent->len += concat_p->tot_len;
} else if (extendlen > 0) {
struct pbuf *p;
LWIP_ASSERT("tcp_write: extension of reference requires reference",
last_unsent != NULL && last_unsent->p != NULL);
for (p = last_unsent->p; p->next != NULL; p = p->next) {
p->tot_len += extendlen;
}
p->tot_len += extendlen;
p->len += extendlen;
last_unsent->len += extendlen;
}
#if TCP_CHECKSUM_ON_COPY
if (concat_chksummed) {
LWIP_ASSERT("tcp_write: concat checksum needs concatenated data",
concat_p != NULL || extendlen > 0);
if (concat_chksum_swapped) {
concat_chksum = SWAP_BYTES_IN_WORD(concat_chksum);
}
tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
&last_unsent->chksum_swapped);
last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
}
#endif
if (last_unsent == NULL) {
pcb->unsent = queue;
} else {
last_unsent->next = queue;
}
pcb->snd_lbb += len;
pcb->snd_buf -= len;
pcb->snd_queuelen = queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE) == 0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK;
memerr:
tcp_set_flags(pcb, TF_NAGLEMEMERR);
TCP_STATS_INC(tcp.memerr);
if (concat_p != NULL) {
pbuf_free(concat_p);
}
if (queue != NULL) {
tcp_segs_free(queue);
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_write: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
return ERR_MEM;
}
err_t
tcp_split_unsent_seg(struct tcp_pcb *pcb, u16_t split)
{
struct tcp_seg *seg = NULL, *useg = NULL;
struct pbuf *p = NULL;
u8_t optlen;
u8_t optflags;
u8_t split_flags;
u8_t remainder_flags;
u16_t remainder;
u16_t offset;
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
struct pbuf *q;
#endif
LWIP_ASSERT("tcp_split_unsent_seg: invalid pcb", pcb != NULL);
useg = pcb->unsent;
if (useg == NULL) {
return ERR_MEM;
}
if (split == 0) {
LWIP_ASSERT("Can't split segment into length 0", 0);
return ERR_VAL;
}
if (useg->len <= split) {
return ERR_OK;
}
LWIP_ASSERT("split <= mss", split <= pcb->mss);
LWIP_ASSERT("useg->len > 0", useg->len > 0);
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: split_unsent_seg: %u\n", (unsigned int)pcb->snd_queuelen));
optflags = useg->flags;
#if TCP_CHECKSUM_ON_COPY
optflags &= ~TF_SEG_DATA_CHECKSUMMED;
#endif
optlen = LWIP_TCP_OPT_LENGTH(optflags);
remainder = useg->len - split;
p = pbuf_alloc(PBUF_TRANSPORT, remainder + optlen, PBUF_RAM);
if (p == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not allocate memory for pbuf remainder %u\n", remainder));
goto memerr;
}
offset = useg->p->tot_len - useg->len + split;
if (pbuf_copy_partial(useg->p, (u8_t *)p->payload + optlen, remainder, offset ) != remainder) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not copy pbuf remainder %u\n", remainder));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
tcp_seg_add_chksum(~inet_chksum((const u8_t *)p->payload + optlen, remainder), remainder,
&chksum, &chksum_swapped);
#endif
split_flags = TCPH_FLAGS(useg->tcphdr);
remainder_flags = 0;
if (split_flags & TCP_PSH) {
split_flags &= ~TCP_PSH;
remainder_flags |= TCP_PSH;
}
if (split_flags & TCP_FIN) {
split_flags &= ~TCP_FIN;
remainder_flags |= TCP_FIN;
}
seg = tcp_create_segment(pcb, p, remainder_flags, lwip_ntohl(useg->tcphdr->seqno) + split, optflags);
if (seg == NULL) {
p = NULL;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not create new TCP segment\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif
pcb->snd_queuelen -= pbuf_clen(useg->p);
pbuf_realloc(useg->p, useg->p->tot_len - remainder);
useg->len -= remainder;
TCPH_SET_FLAG(useg->tcphdr, split_flags);
#if TCP_OVERSIZE_DBGCHECK
useg->oversize_left = 0;
#endif
pcb->snd_queuelen += pbuf_clen(useg->p);
#if TCP_CHECKSUM_ON_COPY
useg->chksum = 0;
useg->chksum_swapped = 0;
q = useg->p;
offset = q->tot_len - useg->len;
while (q != NULL && offset > q->len) {
offset -= q->len;
q = q->next;
}
LWIP_ASSERT("Found start of payload pbuf", q != NULL);
for (; q != NULL; offset = 0, q = q->next) {
tcp_seg_add_chksum(~inet_chksum((const u8_t *)q->payload + offset, q->len - offset), q->len - offset,
&useg->chksum, &useg->chksum_swapped);
}
#endif
pcb->snd_queuelen += pbuf_clen(seg->p);
seg->next = useg->next;
useg->next = seg;
#if TCP_OVERSIZE
if (seg->next == NULL) {
pcb->unsent_oversize = 0;
}
#endif
return ERR_OK;
memerr:
TCP_STATS_INC(tcp.memerr);
LWIP_ASSERT("seg == NULL", seg == NULL);
if (p != NULL) {
pbuf_free(p);
}
return ERR_MEM;
}
err_t
tcp_send_fin(struct tcp_pcb *pcb)
{
LWIP_ASSERT("tcp_send_fin: invalid pcb", pcb != NULL);
if (pcb->unsent != NULL) {
struct tcp_seg *last_unsent;
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 ERR_OK;
}
}
return tcp_enqueue_flags(pcb, TCP_FIN);
}
err_t
tcp_enqueue_flags(struct tcp_pcb *pcb, u8_t flags)
{
struct pbuf *p;
struct tcp_seg *seg;
u8_t optflags = 0;
u8_t optlen = 0;
LWIP_ASSERT("tcp_enqueue_flags: need either TCP_SYN or TCP_FIN in flags (programmer violates API)",
(flags & (TCP_SYN | TCP_FIN)) != 0);
LWIP_ASSERT("tcp_enqueue_flags: invalid pcb", pcb != NULL);
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
if (flags & TCP_SYN) {
optflags = TF_SEG_OPTS_MSS;
#if LWIP_WND_SCALE
if ((pcb->state != SYN_RCVD) || (pcb->flags & TF_WND_SCALE)) {
optflags |= TF_SEG_OPTS_WND_SCALE;
}
#endif
#if LWIP_TCP_SACK_OUT
if ((pcb->state != SYN_RCVD) || (pcb->flags & TF_SACK)) {
optflags |= TF_SEG_OPTS_SACK_PERM;
}
#endif
}
#if LWIP_TCP_TIMESTAMPS
if ((pcb->flags & TF_TIMESTAMP) || ((flags & TCP_SYN) && (pcb->state != SYN_RCVD))) {
optflags |= TF_SEG_OPTS_TS;
}
#endif
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb);
if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
tcp_set_flags(pcb, TF_NAGLEMEMERR);
TCP_STATS_INC(tcp.memerr);
return ERR_MEM;
}
LWIP_ASSERT("tcp_enqueue_flags: check that first pbuf can hold optlen",
(p->len >= optlen));
if ((seg = tcp_create_segment(pcb, p, flags, pcb->snd_lbb, optflags)) == NULL) {
tcp_set_flags(pcb, TF_NAGLEMEMERR);
TCP_STATS_INC(tcp.memerr);
return ERR_MEM;
}
LWIP_ASSERT("seg->tcphdr not aligned", ((mem_ptr_t)seg->tcphdr % LWIP_MIN(MEM_ALIGNMENT, 4)) == 0);
LWIP_ASSERT("tcp_enqueue_flags: invalid segment length", seg->len == 0);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE,
("tcp_enqueue_flags: queueing %"U32_F":%"U32_F" (0x%"X16_F")\n",
lwip_ntohl(seg->tcphdr->seqno),
lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
(u16_t)flags));
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 TCP_OVERSIZE
pcb->unsent_oversize = 0;
#endif
if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
pcb->snd_lbb++;
}
if (flags & TCP_FIN) {
tcp_set_flags(pcb, TF_FIN);
}
pcb->snd_queuelen += pbuf_clen(seg->p);
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: %"S16_F" (after enqueued)\n", pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_enqueue_flags: invalid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
return ERR_OK;
}
#if LWIP_TCP_TIMESTAMPS
static void
tcp_build_timestamp_option(const struct tcp_pcb *pcb, u32_t *opts)
{
LWIP_ASSERT("tcp_build_timestamp_option: invalid pcb", pcb != NULL);
opts[0] = PP_HTONL(0x0101080A);
opts[1] = lwip_htonl(sys_now());
opts[2] = lwip_htonl(pcb->ts_recent);
}
#endif
#if LWIP_TCP_SACK_OUT
static u8_t
tcp_get_num_sacks(const struct tcp_pcb *pcb, u8_t optlen)
{
u8_t num_sacks = 0;
LWIP_ASSERT("tcp_get_num_sacks: invalid pcb", pcb != NULL);
if (pcb->flags & TF_SACK) {
u8_t i;
optlen += 12;
for (i = 0; (i < LWIP_TCP_MAX_SACK_NUM) && (optlen <= TCP_MAX_OPTION_BYTES) &&
LWIP_TCP_SACK_VALID(pcb, i); ++i) {
++num_sacks;
optlen += 8;
}
}
return num_sacks;
}
static void
tcp_build_sack_option(const struct tcp_pcb *pcb, u32_t *opts, u8_t num_sacks)
{
u8_t i;
LWIP_ASSERT("tcp_build_sack_option: invalid pcb", pcb != NULL);
LWIP_ASSERT("tcp_build_sack_option: invalid opts", opts != NULL);
*(opts++) = PP_HTONL(0x01010500 + 2 + num_sacks * 8);
for (i = 0; i < num_sacks; ++i) {
*(opts++) = lwip_htonl(pcb->rcv_sacks[i].left);
*(opts++) = lwip_htonl(pcb->rcv_sacks[i].right);
}
}
#endif
#if LWIP_WND_SCALE
static void
tcp_build_wnd_scale_option(u32_t *opts)
{
LWIP_ASSERT("tcp_build_wnd_scale_option: invalid opts", opts != NULL);
opts[0] = PP_HTONL(0x01030300 | TCP_RCV_SCALE);
}
#endif
err_t
tcp_output(struct tcp_pcb *pcb)
{
struct tcp_seg *seg, *useg;
u32_t wnd, snd_nxt;
err_t err;
struct netif *netif;
#if TCP_CWND_DEBUG
s16_t i = 0;
#endif
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("tcp_output: invalid pcb", pcb != NULL);
LWIP_ASSERT("don't call tcp_output for listen-pcbs",
pcb->state != LISTEN);
if (tcp_input_pcb == pcb) {
return ERR_OK;
}
wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);
seg = pcb->unsent;
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n",
(void *)pcb->unsent));
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F
", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", seg == NULL, ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack));
if (pcb->flags & TF_ACK_NOW) {
return tcp_send_empty_ack(pcb);
}
goto output_done;
} else {
LWIP_DEBUGF(TCP_CWND_DEBUG,
("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack));
}
netif = tcp_route(pcb, &pcb->local_ip, &pcb->remote_ip);
if (netif == NULL) {
return ERR_RTE;
}
if (ip_addr_isany(&pcb->local_ip)) {
const ip_addr_t *local_ip = ip_netif_get_local_ip(netif, &pcb->remote_ip);
if (local_ip == NULL) {
return ERR_RTE;
}
ip_addr_copy(pcb->local_ip, *local_ip);
}
if (lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > 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 &&
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
LWIP_ASSERT("RST not expected here!",
(TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);
if ((tcp_do_output_nagle(pcb) == 0) &&
((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) {
break;
}
#if TCP_CWND_DEBUG
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) + seg->len -
pcb->lastack,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack, i));
++i;
#endif
if (pcb->state != SYN_SENT) {
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
}
err = tcp_output_segment(seg, pcb, netif);
if (err != ERR_OK) {
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return err;
}
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = 0;
#endif
pcb->unsent = seg->next;
if (pcb->state != SYN_SENT) {
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
}
snd_nxt = lwip_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(lwip_ntohl(seg->tcphdr->seqno), lwip_ntohl(useg->tcphdr->seqno))) {
struct tcp_seg **cur_seg = &(pcb->unacked);
while (*cur_seg &&
TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_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(seg);
}
seg = pcb->unsent;
}
#if TCP_OVERSIZE
if (pcb->unsent == NULL) {
pcb->unsent_oversize = 0;
}
#endif
output_done:
tcp_clear_flags(pcb, TF_NAGLEMEMERR);
return ERR_OK;
}
static int
tcp_output_segment_busy(const struct tcp_seg *seg)
{
LWIP_ASSERT("tcp_output_segment_busy: invalid seg", seg != NULL);
if (seg->p->ref != 1) {
return 1;
}
return 0;
}
static err_t
tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb, struct netif *netif)
{
err_t err;
u16_t len;
u32_t *opts;
#if TCP_CHECKSUM_ON_COPY
int seg_chksum_was_swapped = 0;
#endif
LWIP_ASSERT("tcp_output_segment: invalid seg", seg != NULL);
LWIP_ASSERT("tcp_output_segment: invalid pcb", pcb != NULL);
LWIP_ASSERT("tcp_output_segment: invalid netif", netif != NULL);
if (tcp_output_segment_busy(seg)) {
LWIP_DEBUGF(TCP_RTO_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_output_segment: segment busy\n"));
return ERR_OK;
}
seg->tcphdr->ackno = lwip_htonl(pcb->rcv_nxt);
#if LWIP_WND_SCALE
if (seg->flags & TF_SEG_OPTS_WND_SCALE) {
seg->tcphdr->wnd = lwip_htons(TCPWND_MIN16(pcb->rcv_ann_wnd));
} else
#endif
{
seg->tcphdr->wnd = lwip_htons(TCPWND_MIN16(RCV_WND_SCALE(pcb, pcb->rcv_ann_wnd)));
}
pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
opts = (u32_t *)(void *)(seg->tcphdr + 1);
if (seg->flags & TF_SEG_OPTS_MSS) {
u16_t mss;
#if TCP_CALCULATE_EFF_SEND_MSS
mss = tcp_eff_send_mss_netif(TCP_MSS, netif, &pcb->remote_ip);
#else
mss = TCP_MSS;
#endif
*opts = TCP_BUILD_MSS_OPTION(mss);
opts += 1;
}
#if LWIP_TCP_TIMESTAMPS
pcb->ts_lastacksent = pcb->rcv_nxt;
if (seg->flags & TF_SEG_OPTS_TS) {
tcp_build_timestamp_option(pcb, opts);
opts += 3;
}
#endif
#if LWIP_WND_SCALE
if (seg->flags & TF_SEG_OPTS_WND_SCALE) {
tcp_build_wnd_scale_option(opts);
opts += 1;
}
#endif
#if LWIP_TCP_SACK_OUT
if (seg->flags & TF_SEG_OPTS_SACK_PERM) {
*(opts++) = PP_HTONL(0x01010402);
}
#endif
if (pcb->rtime < 0) {
pcb->rtime = 0;
}
if (pcb->rttest == 0) {
pcb->rttest = tcp_ticks;
pcb->rtseq = lwip_ntohl(seg->tcphdr->seqno);
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %"U32_F"\n", pcb->rtseq));
}
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output_segment: %"U32_F":%"U32_F"\n",
lwip_htonl(seg->tcphdr->seqno), lwip_htonl(seg->tcphdr->seqno) +
seg->len));
len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);
if (len == 0) {
MIB2_STATS_INC(mib2.tcpoutsegs);
}
seg->p->len -= len;
seg->p->tot_len -= len;
seg->p->payload = seg->tcphdr;
seg->tcphdr->chksum = 0;
#ifdef LWIP_HOOK_TCP_OUT_ADD_TCPOPTS
opts = LWIP_HOOK_TCP_OUT_ADD_TCPOPTS(seg->p, seg->tcphdr, pcb, opts);
#endif
LWIP_ASSERT("options not filled", (u8_t *)opts == ((u8_t *)(seg->tcphdr + 1)) + LWIP_TCP_OPT_LENGTH_SEGMENT(seg->flags, pcb));
#if CHECKSUM_GEN_TCP
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
#if TCP_CHECKSUM_ON_COPY
u32_t acc;
#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
u16_t chksum_slow = ip_chksum_pseudo(seg->p, IP_PROTO_TCP,
seg->p->tot_len, &pcb->local_ip, &pcb->remote_ip);
#endif
if ((seg->flags & TF_SEG_DATA_CHECKSUMMED) == 0) {
LWIP_ASSERT("data included but not checksummed",
seg->p->tot_len == TCPH_HDRLEN_BYTES(seg->tcphdr));
}
acc = ip_chksum_pseudo_partial(seg->p, IP_PROTO_TCP,
seg->p->tot_len, TCPH_HDRLEN_BYTES(seg->tcphdr), &pcb->local_ip, &pcb->remote_ip);
if (seg->chksum_swapped) {
seg_chksum_was_swapped = 1;
seg->chksum = SWAP_BYTES_IN_WORD(seg->chksum);
seg->chksum_swapped = 0;
}
acc = (u16_t)~acc + seg->chksum;
seg->tcphdr->chksum = (u16_t)~FOLD_U32T(acc);
#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
if (chksum_slow != seg->tcphdr->chksum) {
TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(
("tcp_output_segment: calculated checksum is %"X16_F" instead of %"X16_F"\n",
seg->tcphdr->chksum, chksum_slow));
seg->tcphdr->chksum = chksum_slow;
}
#endif
#else
seg->tcphdr->chksum = ip_chksum_pseudo(seg->p, IP_PROTO_TCP,
seg->p->tot_len, &pcb->local_ip, &pcb->remote_ip);
#endif
}
#endif
TCP_STATS_INC(tcp.xmit);
NETIF_SET_HINTS(netif, &(pcb->netif_hints));
err = ip_output_if(seg->p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
pcb->tos, IP_PROTO_TCP, netif);
NETIF_RESET_HINTS(netif);
#if TCP_CHECKSUM_ON_COPY
if (seg_chksum_was_swapped) {
seg->chksum = SWAP_BYTES_IN_WORD(seg->chksum);
seg->chksum_swapped = 1;
}
#endif
return err;
}
err_t
tcp_rexmit_rto_prepare(struct tcp_pcb *pcb)
{
struct tcp_seg *seg;
LWIP_ASSERT("tcp_rexmit_rto_prepare: invalid pcb", pcb != NULL);
if (pcb->unacked == NULL) {
return ERR_VAL;
}
for (seg = pcb->unacked; seg->next != NULL; seg = seg->next) {
if (tcp_output_segment_busy(seg)) {
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_rexmit_rto: segment busy\n"));
return ERR_VAL;
}
}
if (tcp_output_segment_busy(seg)) {
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_rexmit_rto: segment busy\n"));
return ERR_VAL;
}
seg->next = pcb->unsent;
#if TCP_OVERSIZE_DBGCHECK
if (pcb->unsent == NULL) {
pcb->unsent_oversize = seg->oversize_left;
}
#endif
pcb->unsent = pcb->unacked;
pcb->unacked = NULL;
tcp_set_flags(pcb, TF_RTO);
pcb->rto_end = lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);
pcb->rttest = 0;
return ERR_OK;
}
void
tcp_rexmit_rto_commit(struct tcp_pcb *pcb)
{
LWIP_ASSERT("tcp_rexmit_rto_commit: invalid pcb", pcb != NULL);
if (pcb->nrtx < 0xFF) {
++pcb->nrtx;
}
tcp_output(pcb);
}
void
tcp_rexmit_rto(struct tcp_pcb *pcb)
{
LWIP_ASSERT("tcp_rexmit_rto: invalid pcb", pcb != NULL);
if (tcp_rexmit_rto_prepare(pcb) == ERR_OK) {
tcp_rexmit_rto_commit(pcb);
}
}
err_t
tcp_rexmit(struct tcp_pcb *pcb)
{
struct tcp_seg *seg;
struct tcp_seg **cur_seg;
LWIP_ASSERT("tcp_rexmit: invalid pcb", pcb != NULL);
if (pcb->unacked == NULL) {
return ERR_VAL;
}
seg = pcb->unacked;
if (tcp_output_segment_busy(seg)) {
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_rexmit busy\n"));
return ERR_VAL;
}
pcb->unacked = seg->next;
cur_seg = &(pcb->unsent);
while (*cur_seg &&
TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno))) {
cur_seg = &((*cur_seg)->next );
}
seg->next = *cur_seg;
*cur_seg = seg;
#if TCP_OVERSIZE
if (seg->next == NULL) {
pcb->unsent_oversize = 0;
}
#endif
if (pcb->nrtx < 0xFF) {
++pcb->nrtx;
}
pcb->rttest = 0;
MIB2_STATS_INC(mib2.tcpretranssegs);
return ERR_OK;
}
void
tcp_rexmit_fast(struct tcp_pcb *pcb)
{
LWIP_ASSERT("tcp_rexmit_fast: invalid pcb", pcb != NULL);
if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) {
LWIP_DEBUGF(TCP_FR_DEBUG,
("tcp_receive: dupacks %"U16_F" (%"U32_F
"), fast retransmit %"U32_F"\n",
(u16_t)pcb->dupacks, pcb->lastack,
lwip_ntohl(pcb->unacked->tcphdr->seqno)));
if (tcp_rexmit(pcb) == ERR_OK) {
pcb->ssthresh = LWIP_MIN(pcb->cwnd, pcb->snd_wnd) / 2;
if (pcb->ssthresh < (2U * pcb->mss)) {
LWIP_DEBUGF(TCP_FR_DEBUG,
("tcp_receive: The minimum value for ssthresh %"TCPWNDSIZE_F
" should be min 2 mss %"U16_F"...\n",
pcb->ssthresh, (u16_t)(2 * pcb->mss)));
pcb->ssthresh = 2 * pcb->mss;
}
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
tcp_set_flags(pcb, TF_INFR);
pcb->rtime = 0;
}
}
}
static struct pbuf *
tcp_output_alloc_header_common(u32_t ackno, u16_t optlen, u16_t datalen,
u32_t seqno_be ,
u16_t src_port, u16_t dst_port, u8_t flags, u16_t wnd)
{
struct tcp_hdr *tcphdr;
struct pbuf *p;
p = pbuf_alloc(PBUF_IP, TCP_HLEN + optlen + datalen, PBUF_RAM);
if (p != NULL) {
LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
(p->len >= TCP_HLEN + optlen));
tcphdr = (struct tcp_hdr *)p->payload;
tcphdr->src = lwip_htons(src_port);
tcphdr->dest = lwip_htons(dst_port);
tcphdr->seqno = seqno_be;
tcphdr->ackno = lwip_htonl(ackno);
TCPH_HDRLEN_FLAGS_SET(tcphdr, (5 + optlen / 4), flags);
tcphdr->wnd = lwip_htons(wnd);
tcphdr->chksum = 0;
tcphdr->urgp = 0;
}
return p;
}
static struct pbuf *
tcp_output_alloc_header(struct tcp_pcb *pcb, u16_t optlen, u16_t datalen,
u32_t seqno_be )
{
struct pbuf *p;
LWIP_ASSERT("tcp_output_alloc_header: invalid pcb", pcb != NULL);
p = tcp_output_alloc_header_common(pcb->rcv_nxt, optlen, datalen,
seqno_be, pcb->local_port, pcb->remote_port, TCP_ACK,
TCPWND_MIN16(RCV_WND_SCALE(pcb, pcb->rcv_ann_wnd)));
if (p != NULL) {
pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
}
return p;
}
static void
tcp_output_fill_options(const struct tcp_pcb *pcb, struct pbuf *p, u8_t optflags, u8_t num_sacks)
{
struct tcp_hdr *tcphdr;
u32_t *opts;
u16_t sacks_len = 0;
LWIP_ASSERT("tcp_output_fill_options: invalid pbuf", p != NULL);
tcphdr = (struct tcp_hdr *)p->payload;
opts = (u32_t *)(void *)(tcphdr + 1);
#if LWIP_TCP_TIMESTAMPS
if (optflags & TF_SEG_OPTS_TS) {
tcp_build_timestamp_option(pcb, opts);
opts += 3;
}
#endif
#if LWIP_TCP_SACK_OUT
if (pcb && (num_sacks > 0)) {
tcp_build_sack_option(pcb, opts, num_sacks);
sacks_len = 1 + num_sacks * 2;
opts += sacks_len;
}
#else
LWIP_UNUSED_ARG(num_sacks);
#endif
#ifdef LWIP_HOOK_TCP_OUT_ADD_TCPOPTS
opts = LWIP_HOOK_TCP_OUT_ADD_TCPOPTS(p, tcphdr, pcb, opts);
#endif
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(sacks_len);
LWIP_ASSERT("options not filled", (u8_t *)opts == ((u8_t *)(tcphdr + 1)) + sacks_len * 4 + LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb));
LWIP_UNUSED_ARG(optflags);
LWIP_UNUSED_ARG(opts);
}
static err_t
tcp_output_control_segment(const struct tcp_pcb *pcb, struct pbuf *p,
const ip_addr_t *src, const ip_addr_t *dst)
{
struct netif *netif;
LWIP_ASSERT("tcp_output_control_segment: invalid pbuf", p != NULL);
netif = tcp_route(pcb, src, dst);
if (netif == NULL) {
pbuf_free(p);
return ERR_RTE;
}
return tcp_output_control_segment_netif(pcb, p, src, dst, netif);
}
static err_t
tcp_output_control_segment_netif(const struct tcp_pcb *pcb, struct pbuf *p,
const ip_addr_t *src, const ip_addr_t *dst,
struct netif *netif)
{
err_t err;
u8_t ttl, tos;
LWIP_ASSERT("tcp_output_control_segment_netif: no netif given", netif != NULL);
#if CHECKSUM_GEN_TCP
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
struct tcp_hdr *tcphdr = (struct tcp_hdr *)p->payload;
tcphdr->chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
src, dst);
}
#endif
if (pcb != NULL) {
NETIF_SET_HINTS(netif, LWIP_CONST_CAST(struct netif_hint*, &(pcb->netif_hints)));
ttl = pcb->ttl;
tos = pcb->tos;
} else {
ttl = TCP_TTL;
tos = 0;
}
TCP_STATS_INC(tcp.xmit);
err = ip_output_if(p, src, dst, ttl, tos, IP_PROTO_TCP, netif);
NETIF_RESET_HINTS(netif);
pbuf_free(p);
return err;
}
static struct pbuf *
tcp_rst_common(const struct tcp_pcb *pcb, u32_t seqno, u32_t ackno,
const ip_addr_t *local_ip, const ip_addr_t *remote_ip,
u16_t local_port, u16_t remote_port)
{
struct pbuf *p;
u16_t wnd;
u8_t optlen;
LWIP_ASSERT("tcp_rst: invalid local_ip", local_ip != NULL);
LWIP_ASSERT("tcp_rst: invalid remote_ip", remote_ip != NULL);
LWIP_UNUSED_ARG(local_ip);
LWIP_UNUSED_ARG(remote_ip);
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb);
#if LWIP_WND_SCALE
wnd = PP_HTONS(((TCP_WND >> TCP_RCV_SCALE) & 0xFFFF));
#else
wnd = PP_HTONS(TCP_WND);
#endif
p = tcp_output_alloc_header_common(ackno, optlen, 0, lwip_htonl(seqno), local_port,
remote_port, TCP_RST | TCP_ACK, wnd);
if (p == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_rst: could not allocate memory for pbuf\n"));
return NULL;
}
tcp_output_fill_options(pcb, p, 0, 0);
MIB2_STATS_INC(mib2.tcpoutrsts);
LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_rst: seqno %"U32_F" ackno %"U32_F".\n", seqno, ackno));
return p;
}
void
tcp_rst(const struct tcp_pcb *pcb, u32_t seqno, u32_t ackno,
const ip_addr_t *local_ip, const ip_addr_t *remote_ip,
u16_t local_port, u16_t remote_port)
{
struct pbuf *p;
p = tcp_rst_common(pcb, seqno, ackno, local_ip, remote_ip, local_port, remote_port);
if (p != NULL) {
tcp_output_control_segment(pcb, p, local_ip, remote_ip);
}
}
void
tcp_rst_netif(struct netif *netif, u32_t seqno, u32_t ackno,
const ip_addr_t *local_ip, const ip_addr_t *remote_ip,
u16_t local_port, u16_t remote_port)
{
if (netif) {
struct pbuf *p = tcp_rst_common(NULL, seqno, ackno, local_ip, remote_ip, local_port, remote_port);
if (p != NULL) {
tcp_output_control_segment_netif(NULL, p, local_ip, remote_ip, netif);
}
} else {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_rst_netif: no netif given\n"));
}
}
err_t
tcp_send_empty_ack(struct tcp_pcb *pcb)
{
err_t err;
struct pbuf *p;
u8_t optlen, optflags = 0;
u8_t num_sacks = 0;
LWIP_ASSERT("tcp_send_empty_ack: invalid pcb", pcb != NULL);
#if LWIP_TCP_TIMESTAMPS
if (pcb->flags & TF_TIMESTAMP) {
optflags = TF_SEG_OPTS_TS;
}
#endif
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb);
#if LWIP_TCP_SACK_OUT
if ((num_sacks = tcp_get_num_sacks(pcb, optlen)) > 0) {
optlen += 4 + num_sacks * 8;
}
#endif
p = tcp_output_alloc_header(pcb, optlen, 0, lwip_htonl(pcb->snd_nxt));
if (p == NULL) {
tcp_set_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: (ACK) could not allocate pbuf\n"));
return ERR_BUF;
}
tcp_output_fill_options(pcb, p, optflags, num_sacks);
#if LWIP_TCP_TIMESTAMPS
pcb->ts_lastacksent = pcb->rcv_nxt;
#endif
LWIP_DEBUGF(TCP_OUTPUT_DEBUG,
("tcp_output: sending ACK for %"U32_F"\n", pcb->rcv_nxt));
err = tcp_output_control_segment(pcb, p, &pcb->local_ip, &pcb->remote_ip);
if (err != ERR_OK) {
tcp_set_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
} else {
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
}
return err;
}
err_t
tcp_keepalive(struct tcp_pcb *pcb)
{
err_t err;
struct pbuf *p;
u8_t optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb);
LWIP_ASSERT("tcp_keepalive: invalid pcb", pcb != NULL);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: sending KEEPALIVE probe to "));
ip_addr_debug_print_val(TCP_DEBUG, pcb->remote_ip);
LWIP_DEBUGF(TCP_DEBUG, ("\n"));
LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: tcp_ticks %"U32_F" pcb->tmr %"U32_F" pcb->keep_cnt_sent %"U16_F"\n",
tcp_ticks, pcb->tmr, (u16_t)pcb->keep_cnt_sent));
p = tcp_output_alloc_header(pcb, optlen, 0, lwip_htonl(pcb->snd_nxt - 1));
if (p == NULL) {
LWIP_DEBUGF(TCP_DEBUG,
("tcp_keepalive: could not allocate memory for pbuf\n"));
return ERR_MEM;
}
tcp_output_fill_options(pcb, p, 0, 0);
err = tcp_output_control_segment(pcb, p, &pcb->local_ip, &pcb->remote_ip);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: seqno %"U32_F" ackno %"U32_F" err %d.\n",
pcb->snd_nxt - 1, pcb->rcv_nxt, (int)err));
return err;
}
err_t
tcp_zero_window_probe(struct tcp_pcb *pcb)
{
err_t err;
struct pbuf *p;
struct tcp_hdr *tcphdr;
struct tcp_seg *seg;
u16_t len;
u8_t is_fin;
u32_t snd_nxt;
u8_t optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb);
LWIP_ASSERT("tcp_zero_window_probe: invalid pcb", pcb != NULL);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: sending ZERO WINDOW probe to "));
ip_addr_debug_print_val(TCP_DEBUG, pcb->remote_ip);
LWIP_DEBUGF(TCP_DEBUG, ("\n"));
LWIP_DEBUGF(TCP_DEBUG,
("tcp_zero_window_probe: tcp_ticks %"U32_F
" pcb->tmr %"U32_F" pcb->keep_cnt_sent %"U16_F"\n",
tcp_ticks, pcb->tmr, (u16_t)pcb->keep_cnt_sent));
seg = pcb->unsent;
if (seg == NULL) {
return ERR_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) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: no memory for pbuf\n"));
return ERR_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, seg->p->tot_len - seg->len);
}
snd_nxt = lwip_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);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: seqno %"U32_F
" ackno %"U32_F" err %d.\n",
pcb->snd_nxt - 1, pcb->rcv_nxt, (int)err));
return err;
}
#endif