summaryrefslogtreecommitdiff
path: root/core/lwip/src/core/tcp_out.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/lwip/src/core/tcp_out.c')
-rw-r--r--core/lwip/src/core/tcp_out.c1066
1 files changed, 740 insertions, 326 deletions
diff --git a/core/lwip/src/core/tcp_out.c b/core/lwip/src/core/tcp_out.c
index ddada420..86e09195 100644
--- a/core/lwip/src/core/tcp_out.c
+++ b/core/lwip/src/core/tcp_out.c
@@ -42,382 +42,643 @@
#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
-#include "lwip/tcp.h"
+#include "lwip/tcp_impl.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/sys.h"
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
-#include "lwip/inet.h"
#include "lwip/inet_chksum.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include <string.h>
+/* Define some copy-macros for checksum-on-copy so that the code looks
+ nicer by preventing too many ifdef's. */
+#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 /* TCP_CHECKSUM_ON_COPY*/
+#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 /* TCP_CHECKSUM_ON_COPY*/
+
+/** Define this to 1 for an extra check that the output checksum is valid
+ * (usefule when the checksum is generated by the application, not the stack) */
+#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK 0
+#endif
+
/* Forward declarations.*/
static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb);
-static struct tcp_hdr *
-tcp_output_set_header(struct tcp_pcb *pcb, struct pbuf *p, int optlen,
+/** Allocate a pbuf and create a tcphdr at p->payload, used for output
+ * functions other than the default tcp_output -> tcp_output_segment
+ * (e.g. tcp_send_empty_ack, etc.)
+ *
+ * @param pcb tcp pcb for which to send a packet (used to initialize tcp_hdr)
+ * @param optlen length of header-options
+ * @param datalen length of tcp data to reserve in pbuf
+ * @param seqno_be seqno in network byte order (big-endian)
+ * @return pbuf with p->payload being the tcp_hdr
+ */
+static struct pbuf *
+tcp_output_alloc_header(struct tcp_pcb *pcb, u16_t optlen, u16_t datalen,
u32_t seqno_be /* already in network byte order */)
{
- struct tcp_hdr *tcphdr = p->payload;
- tcphdr->src = htons(pcb->local_port);
- tcphdr->dest = htons(pcb->remote_port);
- tcphdr->seqno = seqno_be;
- tcphdr->ackno = htonl(pcb->rcv_nxt);
- TCPH_FLAGS_SET(tcphdr, TCP_ACK);
- tcphdr->wnd = htons(pcb->rcv_ann_wnd);
- tcphdr->urgp = 0;
- TCPH_HDRLEN_SET(tcphdr, (5 + optlen / 4));
- tcphdr->chksum = 0;
-
- /* If we're sending a packet, update the announced right window edge */
- pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
-
- return tcphdr;
+ struct tcp_hdr *tcphdr;
+ struct pbuf *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 = htons(pcb->local_port);
+ tcphdr->dest = htons(pcb->remote_port);
+ tcphdr->seqno = seqno_be;
+ tcphdr->ackno = htonl(pcb->rcv_nxt);
+ TCPH_HDRLEN_FLAGS_SET(tcphdr, (5 + optlen / 4), TCP_ACK);
+ tcphdr->wnd = htons(pcb->rcv_ann_wnd);
+ tcphdr->chksum = 0;
+ tcphdr->urgp = 0;
+
+ /* If we're sending a packet, update the announced right window edge */
+ pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
+ }
+ return p;
}
/**
- * Called by tcp_close() to send a segment including flags but not data.
+ * Called by tcp_close() to send a segment including FIN flag but not data.
*
* @param pcb the tcp_pcb over which to send a segment
- * @param flags the flags to set in the segment header
* @return ERR_OK if sent, another err_t otherwise
*/
err_t
-tcp_send_ctrl(struct tcp_pcb *pcb, u8_t flags)
+tcp_send_fin(struct tcp_pcb *pcb)
{
+ /* first, try to add the fin to the last unsent segment */
+ 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) {
+ /* no SYN/FIN/RST flag in the header, we can add the FIN flag */
+ TCPH_SET_FLAG(last_unsent->tcphdr, TCP_FIN);
+ return ERR_OK;
+ }
+ }
/* no data, no length, flags, copy=1, no optdata */
- return tcp_enqueue(pcb, NULL, 0, flags, TCP_WRITE_FLAG_COPY, 0);
+ return tcp_enqueue_flags(pcb, TCP_FIN);
}
/**
- * Write data for sending (but does not send it immediately).
+ * Create a TCP segment with prefilled header.
*
- * It waits in the expectation of more data being sent soon (as
- * it can send them more efficiently by combining them together).
- * To prompt the system to send data now, call tcp_output() after
- * calling tcp_write().
- *
- * @param pcb Protocol control block of the TCP connection to enqueue data for.
- * @param data pointer to the data to send
- * @param len length (in bytes) of the data to send
- * @param apiflags combination of following flags :
- * - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
- * - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will be set on last segment sent,
- * @return ERR_OK if enqueued, another err_t on error
- *
- * @see tcp_write()
+ * Called by tcp_write and tcp_enqueue_flags.
+ *
+ * @param pcb Protocol control block for the TCP connection.
+ * @param p pbuf that is used to hold the TCP header.
+ * @param flags TCP flags for header.
+ * @param seqno TCP sequence number of this packet
+ * @param optflags options to include in TCP header
+ * @return a new tcp_seg pointing to p, or NULL.
+ * The TCP header is filled in except ackno and wnd.
+ * p is freed on failure.
*/
-err_t
-tcp_write(struct tcp_pcb *pcb, const void *data, u16_t len, u8_t apiflags)
+static struct tcp_seg *
+tcp_create_segment(struct tcp_pcb *pcb, struct pbuf *p, u8_t flags, u32_t seqno, u8_t optflags)
{
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_write(pcb=%p, data=%p, len=%"U16_F", apiflags=%"U16_F")\n", (void *)pcb,
- data, len, (u16_t)apiflags));
- /* connection is in valid state for data transmission? */
- if (pcb->state == ESTABLISHED ||
- pcb->state == CLOSE_WAIT ||
- pcb->state == SYN_SENT ||
- pcb->state == SYN_RCVD) {
- if (len > 0) {
-#if LWIP_TCP_TIMESTAMPS
- return tcp_enqueue(pcb, (void *)data, len, 0, apiflags,
- pcb->flags & TF_TIMESTAMP ? TF_SEG_OPTS_TS : 0);
-#else
- return tcp_enqueue(pcb, (void *)data, len, 0, apiflags, 0);
-#endif
- }
- return ERR_OK;
- } else {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_STATE | LWIP_DBG_LEVEL_SEVERE, ("tcp_write() called in invalid state\n"));
- return ERR_CONN;
+ struct tcp_seg *seg;
+ u8_t optlen = LWIP_TCP_OPT_LENGTH(optflags);
+
+ if ((seg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG)) == NULL) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_create_segment: no memory.\n"));
+ pbuf_free(p);
+ return NULL;
}
-}
+ seg->flags = optflags;
+ seg->next = NULL;
+ seg->p = p;
+ seg->len = p->tot_len - optlen;
+#if TCP_OVERSIZE_DBGCHECK
+ seg->oversize_left = 0;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#if TCP_CHECKSUM_ON_COPY
+ seg->chksum = 0;
+ seg->chksum_swapped = 0;
+ /* check optflags */
+ LWIP_ASSERT("invalid optflags passed: TF_SEG_DATA_CHECKSUMMED",
+ (optflags & TF_SEG_DATA_CHECKSUMMED) == 0);
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+ /* build TCP header */
+ if (pbuf_header(p, TCP_HLEN)) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("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 = htons(pcb->local_port);
+ seg->tcphdr->dest = htons(pcb->remote_port);
+ seg->tcphdr->seqno = htonl(seqno);
+ /* ackno is set in tcp_output */
+ TCPH_HDRLEN_FLAGS_SET(seg->tcphdr, (5 + optlen / 4), flags);
+ /* wnd and chksum are set in tcp_output */
+ seg->tcphdr->urgp = 0;
+ return seg;
+}
/**
- * Enqueue data and/or TCP options for transmission
+ * Allocate a PBUF_RAM pbuf, perhaps with extra space at the end.
*
- * Called by tcp_connect(), tcp_listen_input(), tcp_send_ctrl() and tcp_write().
+ * This function is like pbuf_alloc(layer, length, PBUF_RAM) except
+ * there may be extra bytes available at the end.
*
- * @param pcb Protocol control block for the TCP connection to enqueue data for.
- * @param arg Pointer to the data to be enqueued for sending.
- * @param len Data length in bytes
- * @param flags tcp header flags to set in the outgoing segment
- * @param apiflags combination of following flags :
- * - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
- * - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will be set on last segment sent,
- * @param optflags options to include in segment later on (see definition of struct tcp_seg)
+ * @param layer flag to define header size.
+ * @param length size of the pbuf's payload.
+ * @param max_length maximum usable size of payload+oversize.
+ * @param oversize pointer to a u16_t that will receive the number of usable tail bytes.
+ * @param pcb The TCP connection that willo enqueue the pbuf.
+ * @param apiflags API flags given to tcp_write.
+ * @param first_seg true when this pbuf will be used in the first enqueued segment.
+ * @param
*/
-err_t
-tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len,
- u8_t flags, u8_t apiflags, u8_t optflags)
+#if TCP_OVERSIZE
+static struct pbuf *
+tcp_pbuf_prealloc(pbuf_layer layer, u16_t length, u16_t max_length,
+ u16_t *oversize, struct tcp_pcb *pcb, u8_t apiflags,
+ u8_t first_seg)
{
struct pbuf *p;
- struct tcp_seg *seg, *useg, *queue;
- u32_t seqno;
- u16_t left, seglen;
- void *ptr;
- u16_t queuelen;
- u8_t optlen;
+ u16_t alloc = length;
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+ LWIP_UNUSED_ARG(max_length);
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(apiflags);
+ LWIP_UNUSED_ARG(first_seg);
+ /* always create MSS-sized pbufs */
+ alloc = TCP_MSS;
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+ if (length < max_length) {
+ /* Should we allocate an oversized pbuf, or just the minimum
+ * length required? If tcp_write is going to be called again
+ * before this segment is transmitted, we want the oversized
+ * buffer. If the segment will be transmitted immediately, we can
+ * save memory by allocating only length. We use a simple
+ * heuristic based on the following information:
+ *
+ * Did the user set TCP_WRITE_FLAG_MORE?
+ *
+ * Will the Nagle algorithm defer transmission of this segment?
+ */
+ 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(length + TCP_OVERSIZE));
+ }
+ }
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+ p = pbuf_alloc(layer, alloc, PBUF_RAM);
+ if (p == NULL) {
+ return NULL;
+ }
+ LWIP_ASSERT("need unchained pbuf", p->next == NULL);
+ *oversize = p->len - length;
+ /* trim p->len to the currently used size */
+ p->len = p->tot_len = length;
+ return p;
+}
+#else /* TCP_OVERSIZE */
+#define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length), PBUF_RAM)
+#endif /* TCP_OVERSIZE */
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG,
- ("tcp_enqueue(pcb=%p, arg=%p, len=%"U16_F", flags=%"X16_F", apiflags=%"U16_F")\n",
- (void *)pcb, arg, len, (u16_t)flags, (u16_t)apiflags));
- LWIP_ERROR("tcp_enqueue: packet needs payload, options, or SYN/FIN (programmer violates API)",
- ((len != 0) || (optflags != 0) || ((flags & (TCP_SYN | TCP_FIN)) != 0)),
- return ERR_ARG;);
- LWIP_ERROR("tcp_enqueue: len != 0 || arg == NULL (programmer violates API)",
- ((len != 0) || (arg == NULL)), return ERR_ARG;);
+#if TCP_CHECKSUM_ON_COPY
+/** Add a checksum of newly added data to the segment */
+static void
+tcp_seg_add_chksum(u16_t chksum, u16_t len, u16_t *seg_chksum,
+ u8_t *seg_chksum_swapped)
+{
+ u32_t helper;
+ /* add chksum to old chksum and fold to u16_t */
+ 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 /* TCP_CHECKSUM_ON_COPY */
+
+/** Checks if tcp_write is allowed or not (checks state, snd_buf and snd_queuelen).
+ *
+ * @param pcb the tcp pcb to check for
+ * @param len length of data to send (checked agains snd_buf)
+ * @return ERR_OK if tcp_write is allowed to proceed, another err_t otherwise
+ */
+static err_t
+tcp_write_checks(struct tcp_pcb *pcb, u16_t len)
+{
+ /* connection is in invalid state for data transmission? */
+ 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;
+ }
/* fail on too much data */
if (len > pcb->snd_buf) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_WARNING,
- ("tcp_enqueue: too much data (len=%"U16_F" > snd_buf=%"U16_F")\n", len, pcb->snd_buf));
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_write: too much data (len=%"U16_F" > snd_buf=%"U16_F")\n",
+ len, pcb->snd_buf));
pcb->flags |= TF_NAGLEMEMERR;
return ERR_MEM;
}
- left = len;
- ptr = arg;
- optlen = LWIP_TCP_OPT_LENGTH(optflags);
-
- /* seqno will be the sequence number of the first segment enqueued
- * by the call to this function. */
- seqno = pcb->snd_lbb;
-
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
/* If total number of pbufs on the unsent/unacked queues exceeds the
* configured maximum, return an error */
- queuelen = pcb->snd_queuelen;
/* check for configured max queuelen and possible overflow */
- if ((queuelen >= TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_WARNING,
- ("tcp_enqueue: too long queue %"U16_F" (max %"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
+ if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n",
+ pcb->snd_queuelen, TCP_SND_QUEUELEN));
TCP_STATS_INC(tcp.memerr);
pcb->flags |= TF_NAGLEMEMERR;
return ERR_MEM;
}
- if (queuelen != 0) {
- LWIP_ASSERT("tcp_enqueue: pbufs on queue => at least one queue non-empty",
+ 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_enqueue: no pbufs on queue => both queues empty",
+ LWIP_ASSERT("tcp_write: no pbufs on queue => both queues empty",
pcb->unacked == NULL && pcb->unsent == NULL);
}
+ return ERR_OK;
+}
- /* First, break up the data into segments and tuck them together in
- * the local "queue" variable. */
- useg = queue = seg = NULL;
- seglen = 0;
- while (queue == NULL || left > 0) {
- /* The segment length (including options) should be at most the MSS */
- seglen = left > (pcb->mss - optlen) ? (pcb->mss - optlen) : left;
-
- /* Allocate memory for tcp_seg, and fill in fields. */
- seg = memp_malloc(MEMP_TCP_SEG);
- if (seg == NULL) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
- ("tcp_enqueue: could not allocate memory for tcp_seg\n"));
- goto memerr;
- }
- seg->next = NULL;
- seg->p = NULL;
+/**
+ * Write data for sending (but does not send it immediately).
+ *
+ * It waits in the expectation of more data being sent soon (as
+ * it can send them more efficiently by combining them together).
+ * To prompt the system to send data now, call tcp_output() after
+ * calling tcp_write().
+ *
+ * @param pcb Protocol control block for the TCP connection to enqueue data for.
+ * @param arg Pointer to the data to be enqueued for sending.
+ * @param len Data length in bytes
+ * @param apiflags combination of following flags :
+ * - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
+ * - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will be set on last segment sent,
+ * @return ERR_OK if enqueued, another err_t on error
+ */
+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; /* position in 'arg' data */
+ u16_t queuelen;
+ u8_t optlen = 0;
+ u8_t optflags = 0;
+#if TCP_OVERSIZE
+ u16_t oversize = 0;
+ u16_t oversize_used = 0;
+#endif /* TCP_OVERSIZE */
+#if TCP_CHECKSUM_ON_COPY
+ u16_t concat_chksum = 0;
+ u8_t concat_chksum_swapped = 0;
+ u16_t concat_chksummed = 0;
+#endif /* TCP_CHECKSUM_ON_COPY */
+ err_t err;
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+ /* Always copy to try to create single pbufs for TX */
+ apiflags |= TCP_WRITE_FLAG_COPY;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+ 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;
- /* first segment of to-be-queued data? */
- if (queue == NULL) {
- queue = seg;
+#if LWIP_TCP_TIMESTAMPS
+ if ((pcb->flags & TF_TIMESTAMP)) {
+ optflags = TF_SEG_OPTS_TS;
+ optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
+ }
+#endif /* LWIP_TCP_TIMESTAMPS */
+
+
+ /*
+ * TCP segmentation is done in three phases with increasing complexity:
+ *
+ * 1. Copy data directly into an oversized pbuf.
+ * 2. Chain a new pbuf to the end of pcb->unsent.
+ * 3. Create new segments.
+ *
+ * We may run out of memory at any point. In that case we must
+ * return ERR_MEM and not change anything in pcb. Therefore, all
+ * changes are recorded in local variables and committed at the end
+ * of the function. Some pcb fields are maintained in local copies:
+ *
+ * queuelen = pcb->snd_queuelen
+ * oversize = pcb->unsent_oversize
+ *
+ * These variables are set consistently by the phases:
+ *
+ * seg points to the last segment tampered with.
+ *
+ * pos records progress as data is segmented.
+ */
+
+ /* Find the tail of the unsent queue. */
+ if (pcb->unsent != NULL) {
+ u16_t space;
+ u16_t unsent_optlen;
+
+ /* @todo: this could be sped up by keeping last_unsent in the pcb */
+ for (last_unsent = pcb->unsent; last_unsent->next != NULL;
+ last_unsent = last_unsent->next);
+
+ /* Usable space at the end of the last unsent segment */
+ unsent_optlen = LWIP_TCP_OPT_LENGTH(last_unsent->flags);
+ space = pcb->mss - (last_unsent->len + unsent_optlen);
+
+ /*
+ * Phase 1: Copy data directly into an oversized pbuf.
+ *
+ * The number of bytes copied is recorded in the oversize_used
+ * variable. The actual copying is done at the bottom of the
+ * function.
+ */
+#if TCP_OVERSIZE
+#if TCP_OVERSIZE_DBGCHECK
+ /* check that pcb->unsent_oversize matches last_unsent->unsent_oversize */
+ LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
+ pcb->unsent_oversize == last_unsent->oversize_left);
+#endif /* TCP_OVERSIZE_DBGCHECK */
+ oversize = pcb->unsent_oversize;
+ if (oversize > 0) {
+ LWIP_ASSERT("inconsistent oversize vs. space", oversize_used <= space);
+ seg = last_unsent;
+ oversize_used = oversize < len ? oversize : len;
+ pos += oversize_used;
+ oversize -= oversize_used;
+ space -= oversize_used;
}
- /* subsequent segments of to-be-queued data */
- else {
- /* Attach the segment to the end of the queued segments */
- LWIP_ASSERT("useg != NULL", useg != NULL);
- useg->next = seg;
+ /* now we are either finished or oversize is zero */
+ LWIP_ASSERT("inconsistend oversize vs. len", (oversize == 0) || (pos == len));
+#endif /* TCP_OVERSIZE */
+
+ /*
+ * Phase 2: Chain a new pbuf to the end of pcb->unsent.
+ *
+ * We don't extend segments containing SYN/FIN flags or options
+ * (len==0). The new pbuf is kept in concat_p and pbuf_cat'ed at
+ * the end.
+ */
+ if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
+ u16_t seglen = space < len - pos ? space : len - pos;
+ seg = last_unsent;
+
+ /* Create a pbuf with a copy or reference to seglen bytes. We
+ * can use PBUF_RAW here since the data appears in the middle of
+ * a segment. A header will never be prepended. */
+ if (apiflags & TCP_WRITE_FLAG_COPY) {
+ /* Data is copied */
+ if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
+ ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
+ seglen));
+ goto memerr;
+ }
+#if TCP_OVERSIZE_DBGCHECK
+ last_unsent->oversize_left = oversize;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+ TCP_DATA_COPY2(concat_p->payload, (u8_t*)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
+#if TCP_CHECKSUM_ON_COPY
+ concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+ } else {
+ /* Data is not copied */
+ if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
+ ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
+ goto memerr;
+ }
+#if TCP_CHECKSUM_ON_COPY
+ /* calculate the checksum of nocopy-data */
+ tcp_seg_add_chksum(~inet_chksum((u8_t*)arg + pos, seglen), seglen,
+ &concat_chksum, &concat_chksum_swapped);
+ concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+ /* reference the non-volatile payload data */
+ concat_p->payload = (u8_t*)arg + pos;
+ }
+
+ pos += seglen;
+ queuelen += pbuf_clen(concat_p);
}
- /* remember last segment of to-be-queued data for next iteration */
- useg = seg;
+ } else {
+#if TCP_OVERSIZE
+ LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
+ pcb->unsent_oversize == 0);
+#endif /* TCP_OVERSIZE */
+ }
+
+ /*
+ * Phase 3: Create new segments.
+ *
+ * The new segments are chained together in the local 'queue'
+ * variable, ready to be appended to pcb->unsent.
+ */
+ while (pos < len) {
+ struct pbuf *p;
+ u16_t left = len - pos;
+ u16_t max_len = pcb->mss - optlen;
+ u16_t seglen = left > max_len ? max_len : left;
+#if TCP_CHECKSUM_ON_COPY
+ u16_t chksum = 0;
+ u8_t chksum_swapped = 0;
+#endif /* TCP_CHECKSUM_ON_COPY */
- /* If copy is set, memory should be allocated
- * and data copied into pbuf, otherwise data comes from
- * ROM or other static memory, and need not be copied. */
if (apiflags & TCP_WRITE_FLAG_COPY) {
- if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen + optlen, PBUF_RAM)) == NULL) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
- ("tcp_enqueue : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
+ /* If copy is set, memory should be allocated and data copied
+ * into pbuf */
+ if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, pcb->mss, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
goto memerr;
}
- LWIP_ASSERT("check that first pbuf can hold the complete seglen",
- (seg->p->len >= seglen + optlen));
- queuelen += pbuf_clen(seg->p);
- if (arg != NULL) {
- MEMCPY((char *)seg->p->payload + optlen, ptr, seglen);
- }
- seg->dataptr = seg->p->payload;
- }
- /* do not copy data */
- else {
- /* First, allocate a pbuf for the headers. */
- if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
- ("tcp_enqueue: could not allocate memory for header pbuf\n"));
+ LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
+ (p->len >= seglen));
+ TCP_DATA_COPY2((char *)p->payload + optlen, (u8_t*)arg + pos, seglen, &chksum, &chksum_swapped);
+ } else {
+ /* Copy is not set: First allocate a pbuf for holding the data.
+ * Since the referenced data is available at least until it is
+ * sent out on the link (as it has to be ACKed by the remote
+ * party) we can safely use PBUF_ROM instead of PBUF_REF here.
+ */
+ struct pbuf *p2;
+#if TCP_OVERSIZE
+ LWIP_ASSERT("oversize == 0", oversize == 0);
+#endif /* TCP_OVERSIZE */
+ if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
- queuelen += pbuf_clen(seg->p);
-
- /* Second, allocate a pbuf for holding the data.
- * since the referenced data is available at least until it is sent out on the
- * link (as it has to be ACKed by the remote party) we can safely use PBUF_ROM
- * instead of PBUF_REF here.
- */
- if (left > 0) {
- if ((p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
- /* If allocation fails, we have to deallocate the header pbuf as well. */
- pbuf_free(seg->p);
- seg->p = NULL;
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
- ("tcp_enqueue: could not allocate memory for zero-copy pbuf\n"));
- goto memerr;
- }
- ++queuelen;
- /* reference the non-volatile payload data */
- p->payload = ptr;
- seg->dataptr = ptr;
-
- /* Concatenate the headers and data pbufs together. */
- pbuf_cat(seg->p/*header*/, p/*data*/);
- p = NULL;
+#if TCP_CHECKSUM_ON_COPY
+ /* calculate the checksum of nocopy-data */
+ chksum = ~inet_chksum((u8_t*)arg + pos, seglen);
+#endif /* TCP_CHECKSUM_ON_COPY */
+ /* reference the non-volatile payload data */
+ p2->payload = (u8_t*)arg + pos;
+
+ /* Second, allocate a pbuf for the headers. */
+ if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
+ /* If allocation fails, we have to deallocate the data pbuf as
+ * well. */
+ pbuf_free(p2);
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for header pbuf\n"));
+ goto memerr;
}
+ /* Concatenate the headers and data pbufs together. */
+ pbuf_cat(p/*header*/, p2/*data*/);
}
+ queuelen += pbuf_clen(p);
+
/* Now that there are more segments queued, we check again if the
- length of the queue exceeds the configured maximum or overflows. */
+ * length of the queue exceeds the configured maximum or
+ * overflows. */
if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
- ("tcp_enqueue: queue too long %"U16_F" (%"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: queue too long %"U16_F" (%"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
+ pbuf_free(p);
goto memerr;
}
- seg->len = seglen;
-
- /* build TCP header */
- if (pbuf_header(seg->p, TCP_HLEN)) {
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_enqueue: no room for TCP header in pbuf.\n"));
- TCP_STATS_INC(tcp.err);
+ if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
goto memerr;
}
- seg->tcphdr = seg->p->payload;
- seg->tcphdr->src = htons(pcb->local_port);
- seg->tcphdr->dest = htons(pcb->remote_port);
- seg->tcphdr->seqno = htonl(seqno);
- seg->tcphdr->urgp = 0;
- TCPH_FLAGS_SET(seg->tcphdr, flags);
- /* don't fill in tcphdr->ackno and tcphdr->wnd until later */
-
- seg->flags = optflags;
-
- /* Set the length of the header */
- TCPH_HDRLEN_SET(seg->tcphdr, (5 + optlen / 4));
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_enqueue: queueing %"U32_F":%"U32_F" (0x%"X16_F")\n",
+#if TCP_OVERSIZE_DBGCHECK
+ seg->oversize_left = oversize;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#if TCP_CHECKSUM_ON_COPY
+ seg->chksum = chksum;
+ seg->chksum_swapped = chksum_swapped;
+ seg->flags |= TF_SEG_DATA_CHECKSUMMED;
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+ /* first segment of to-be-queued data? */
+ if (queue == NULL) {
+ queue = seg;
+ } else {
+ /* Attach the segment to the end of the queued segments */
+ LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
+ prev_seg->next = seg;
+ }
+ /* remember last segment of to-be-queued data for next iteration */
+ prev_seg = seg;
+
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
ntohl(seg->tcphdr->seqno),
- ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
- (u16_t)flags));
+ ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
- left -= seglen;
- seqno += seglen;
- ptr = (void *)((u8_t *)ptr + seglen);
+ pos += seglen;
}
- /* Now that the data to be enqueued has been broken up into TCP
- segments in the queue variable, we add them to the end of the
- pcb->unsent queue. */
- if (pcb->unsent == NULL) {
- useg = NULL;
- }
- else {
- for (useg = pcb->unsent; useg->next != NULL; useg = useg->next);
- }
- /* { useg is last segment on the unsent queue, NULL if list is empty } */
-
- /* If there is room in the last pbuf on the unsent queue,
- chain the first pbuf on the queue together with that. */
- if (useg != NULL &&
- TCP_TCPLEN(useg) != 0 &&
- !(TCPH_FLAGS(useg->tcphdr) & (TCP_SYN | TCP_FIN)) &&
- (!(flags & (TCP_SYN | TCP_FIN)) || (flags == TCP_FIN)) &&
- /* fit within max seg size */
- (useg->len + queue->len <= pcb->mss) &&
- /* only concatenate segments with the same options */
- (useg->flags == queue->flags) &&
- /* segments are consecutive */
- (ntohl(useg->tcphdr->seqno) + useg->len == ntohl(queue->tcphdr->seqno)) ) {
- /* Remove TCP header from first segment of our to-be-queued list */
- if(pbuf_header(queue->p, -(TCP_HLEN + optlen))) {
- /* Can we cope with this failing? Just assert for now */
- LWIP_ASSERT("pbuf_header failed\n", 0);
- TCP_STATS_INC(tcp.err);
- goto memerr;
- }
- if (queue->p->len == 0) {
- /* free the first (header-only) pbuf if it is now empty (contained only headers) */
- struct pbuf *old_q = queue->p;
- queue->p = queue->p->next;
- old_q->next = NULL;
- queuelen--;
- pbuf_free(old_q);
- }
- if (flags & TCP_FIN) {
- /* the new segment contains only FIN, no data -> put the FIN into the last segment */
- LWIP_ASSERT("FIN enqueued together with data", queue->p == NULL && queue->len == 0);
- TCPH_SET_FLAG(useg->tcphdr, TCP_FIN);
- } else {
- LWIP_ASSERT("zero-length pbuf", (queue->p != NULL) && (queue->p->len > 0));
- pbuf_cat(useg->p, queue->p);
- useg->len += queue->len;
- useg->next = queue->next;
- }
+ /*
+ * All three segmentation phases were successful. We can commit the
+ * transaction.
+ */
- LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("tcp_enqueue: chaining segments, new len %"U16_F"\n", useg->len));
- if (seg == queue) {
- seg = useg;
- seglen = useg->len;
+ /*
+ * Phase 1: If data has been added to the preallocated tail of
+ * last_unsent, we update the length fields of the pbuf chain.
+ */
+#if TCP_OVERSIZE
+ if (oversize_used > 0) {
+ struct pbuf *p;
+ /* Bump tot_len of whole chain, len of tail */
+ 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;
+ }
}
- memp_free(MEMP_TCP_SEG, queue);
+ last_unsent->len += oversize_used;
+#if TCP_OVERSIZE_DBGCHECK
+ last_unsent->oversize_left -= oversize_used;
+#endif /* TCP_OVERSIZE_DBGCHECK */
}
- else {
- /* empty list */
- if (useg == NULL) {
- /* initialize list with this segment */
- pcb->unsent = queue;
- }
- /* enqueue segment */
- else {
- useg->next = queue;
+ pcb->unsent_oversize = oversize;
+#endif /* TCP_OVERSIZE */
+
+ /*
+ * Phase 2: concat_p can be concatenated onto last_unsent->p
+ */
+ 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;
+#if TCP_CHECKSUM_ON_COPY
+ if (concat_chksummed) {
+ tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
+ &last_unsent->chksum_swapped);
+ last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
}
+#endif /* TCP_CHECKSUM_ON_COPY */
}
- if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
- ++len;
- }
- if (flags & TCP_FIN) {
- pcb->flags |= TF_FIN;
+
+ /*
+ * Phase 3: Append queue to pcb->unsent. Queue may be NULL, but that
+ * is harmless
+ */
+ if (last_unsent == NULL) {
+ pcb->unsent = queue;
+ } else {
+ last_unsent->next = queue;
}
- pcb->snd_lbb += len;
+ /*
+ * Finally update the pcb state.
+ */
+ pcb->snd_lbb += len;
pcb->snd_buf -= len;
-
- /* update number of segments on the queues */
pcb->snd_queuelen = queuelen;
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: %"S16_F" (after enqueued)\n", pcb->snd_queuelen));
+
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
+ pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
- LWIP_ASSERT("tcp_enqueue: valid queue length",
- pcb->unacked != NULL || pcb->unsent != NULL);
+ LWIP_ASSERT("tcp_write: valid queue length",
+ pcb->unacked != NULL || pcb->unsent != NULL);
}
- /* Set the PSH flag in the last segment that we enqueued, but only
- if the segment has data (indicated by seglen > 0). */
- if (seg != NULL && seglen > 0 && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {
+ /* Set the PSH flag in the last segment that we enqueued. */
+ if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
@@ -426,17 +687,130 @@ memerr:
pcb->flags |= 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_enqueue: valid queue length", pcb->unacked != NULL ||
+ LWIP_ASSERT("tcp_write: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
- LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_enqueue: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_write: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
return ERR_MEM;
}
+/**
+ * Enqueue TCP options for transmission.
+ *
+ * Called by tcp_connect(), tcp_listen_input(), and tcp_send_ctrl().
+ *
+ * @param pcb Protocol control block for the TCP connection.
+ * @param flags TCP header flags to set in the outgoing segment.
+ * @param optdata pointer to TCP options, or NULL.
+ * @param optlen length of TCP options in bytes.
+ */
+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_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+
+ LWIP_ASSERT("tcp_enqueue_flags: need either TCP_SYN or TCP_FIN in flags (programmer violates API)",
+ (flags & (TCP_SYN | TCP_FIN)) != 0);
+
+ /* check for configured max queuelen and possible overflow */
+ if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue_flags: too long queue %"U16_F" (max %"U16_F")\n",
+ pcb->snd_queuelen, TCP_SND_QUEUELEN));
+ TCP_STATS_INC(tcp.memerr);
+ pcb->flags |= TF_NAGLEMEMERR;
+ return ERR_MEM;
+ }
+
+ if (flags & TCP_SYN) {
+ optflags = TF_SEG_OPTS_MSS;
+ }
+#if LWIP_TCP_TIMESTAMPS
+ if ((pcb->flags & TF_TIMESTAMP)) {
+ optflags |= TF_SEG_OPTS_TS;
+ }
+#endif /* LWIP_TCP_TIMESTAMPS */
+ optlen = LWIP_TCP_OPT_LENGTH(optflags);
+
+ /* tcp_enqueue_flags is always called with either SYN or FIN in flags.
+ * We need one available snd_buf byte to do that.
+ * This means we can't send FIN while snd_buf==0. A better fix would be to
+ * not include SYN and FIN sequence numbers in the snd_buf count. */
+ if (pcb->snd_buf == 0) {
+ LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue_flags: no send buffer available\n"));
+ TCP_STATS_INC(tcp.memerr);
+ return ERR_MEM;
+ }
+
+ /* Allocate pbuf with room for TCP header + options */
+ if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
+ pcb->flags |= 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));
+
+ /* Allocate memory for tcp_seg, and fill in fields. */
+ if ((seg = tcp_create_segment(pcb, p, flags, pcb->snd_lbb, optflags)) == NULL) {
+ pcb->flags |= TF_NAGLEMEMERR;
+ TCP_STATS_INC(tcp.memerr);
+ return ERR_MEM;
+ }
+ LWIP_ASSERT("seg->tcphdr not aligned", ((mem_ptr_t)seg->tcphdr % MEM_ALIGNMENT) == 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",
+ ntohl(seg->tcphdr->seqno),
+ ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
+ (u16_t)flags));
+
+ /* Now append seg to pcb->unsent queue */
+ 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
+ /* The new unsent tail has no space */
+ pcb->unsent_oversize = 0;
+#endif /* TCP_OVERSIZE */
+
+ /* SYN and FIN bump the sequence number */
+ if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
+ pcb->snd_lbb++;
+ /* optlen does not influence snd_buf */
+ pcb->snd_buf--;
+ }
+ if (flags & TCP_FIN) {
+ pcb->flags |= TF_FIN;
+ }
+
+ /* update number of segments on the queues */
+ 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
/* Build a timestamp option (12 bytes long) at the specified options pointer)
@@ -448,7 +822,7 @@ static void
tcp_build_timestamp_option(struct tcp_pcb *pcb, u32_t *opts)
{
/* Pad with two NOP options to make everything nicely aligned */
- opts[0] = htonl(0x0101080A);
+ opts[0] = PP_HTONL(0x0101080A);
opts[1] = htonl(sys_now());
opts[2] = htonl(pcb->ts_recent);
}
@@ -470,18 +844,18 @@ tcp_send_empty_ack(struct tcp_pcb *pcb)
optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
}
#endif
- p = pbuf_alloc(PBUF_IP, TCP_HLEN + optlen, PBUF_RAM);
+
+ p = tcp_output_alloc_header(pcb, optlen, 0, htonl(pcb->snd_nxt));
if (p == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: (ACK) could not allocate pbuf\n"));
return ERR_BUF;
}
+ tcphdr = (struct tcp_hdr *)p->payload;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG,
("tcp_output: sending ACK for %"U32_F"\n", pcb->rcv_nxt));
/* remove ACK flags from the PCB, as we send an empty ACK now */
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
- tcphdr = tcp_output_set_header(pcb, p, optlen, htonl(pcb->snd_nxt));
-
/* NB. MSS option is only sent on SYNs, so ignore it here */
#if LWIP_TCP_TIMESTAMPS
pcb->ts_lastacksent = pcb->rcv_nxt;
@@ -581,10 +955,10 @@ tcp_output(struct tcp_pcb *pcb)
(TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);
/* Stop sending if the nagle algorithm would prevent it
* Don't stop:
- * - if tcp_enqueue had a memory error before (prevent delayed ACK timeout) or
+ * - if tcp_write had a memory error before (prevent delayed ACK timeout) or
* - if FIN was already enqueued for this PCB (SYN is always alone in a segment -
* either seg->next != NULL or pcb->unacked == NULL;
- * RST is no sent using tcp_enqueue/tcp_output.
+ * RST is no sent using tcp_write/tcp_output.
*/
if((tcp_do_output_nagle(pcb) == 0) &&
((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)){
@@ -623,7 +997,7 @@ tcp_output(struct tcp_pcb *pcb)
/* In the case of fast retransmit, the packet should not go to the tail
* of the unacked queue, but rather somewhere before it. We need to check for
* this case. -STJ Jul 27, 2004 */
- if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))){
+ if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))) {
/* add segment to before tail of unacked list, keeping the list sorted */
struct tcp_seg **cur_seg = &(pcb->unacked);
while (*cur_seg &&
@@ -644,6 +1018,12 @@ tcp_output(struct tcp_pcb *pcb)
}
seg = pcb->unsent;
}
+#if TCP_OVERSIZE
+ if (pcb->unsent == NULL) {
+ /* last unsent has been removed, reset unsent_oversize */
+ pcb->unsent_oversize = 0;
+ }
+#endif /* TCP_OVERSIZE */
if (seg != NULL && pcb->persist_backoff == 0 &&
ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > pcb->snd_wnd) {
@@ -683,7 +1063,8 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
/* Add any requested options. NB MSS option is only set on SYN
packets, so ignore it here */
- opts = (u32_t *)(seg->tcphdr + 1);
+ LWIP_ASSERT("seg->tcphdr not aligned", ((mem_ptr_t)seg->tcphdr % MEM_ALIGNMENT) == 0);
+ opts = (u32_t *)(void *)(seg->tcphdr + 1);
if (seg->flags & TF_SEG_OPTS_MSS) {
TCP_BUILD_MSS_OPTION(*opts);
opts += 1;
@@ -697,6 +1078,12 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
}
#endif
+ /* Set retransmission timer running if it is not currently enabled
+ This must be set before checking the route. */
+ if (pcb->rtime == -1) {
+ pcb->rtime = 0;
+ }
+
/* If we don't have a local IP address, we get one by
calling ip_route(). */
if (ip_addr_isany(&(pcb->local_ip))) {
@@ -704,13 +1091,9 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
if (netif == NULL) {
return;
}
- ip_addr_set(&(pcb->local_ip), &(netif->ip_addr));
+ ip_addr_copy(pcb->local_ip, netif->ip_addr);
}
- /* Set retransmission timer running if it is not currently enabled */
- if(pcb->rtime == -1)
- pcb->rtime = 0;
-
if (pcb->rttest == 0) {
pcb->rttest = tcp_ticks;
pcb->rtseq = ntohl(seg->tcphdr->seqno);
@@ -730,11 +1113,45 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
seg->tcphdr->chksum = 0;
#if CHECKSUM_GEN_TCP
- seg->tcphdr->chksum = inet_chksum_pseudo(seg->p,
- &(pcb->local_ip),
+#if TCP_CHECKSUM_ON_COPY
+ {
+ u32_t acc;
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+ u16_t chksum_slow = inet_chksum_pseudo(seg->p, &(pcb->local_ip),
+ &(pcb->remote_ip),
+ IP_PROTO_TCP, seg->p->tot_len);
+#endif /* TCP_CHECKSUM_ON_COPY_SANITY_CHECK */
+ if ((seg->flags & TF_SEG_DATA_CHECKSUMMED) == 0) {
+ LWIP_ASSERT("data included but not checksummed",
+ seg->p->tot_len == (TCPH_HDRLEN(seg->tcphdr) * 4));
+ }
+
+ /* rebuild TCP header checksum (TCP header changes for retransmissions!) */
+ acc = inet_chksum_pseudo_partial(seg->p, &(pcb->local_ip),
&(pcb->remote_ip),
- IP_PROTO_TCP, seg->p->tot_len);
-#endif
+ IP_PROTO_TCP, seg->p->tot_len, TCPH_HDRLEN(seg->tcphdr) * 4);
+ /* add payload checksum */
+ if (seg->chksum_swapped) {
+ seg->chksum = SWAP_BYTES_IN_WORD(seg->chksum);
+ seg->chksum_swapped = 0;
+ }
+ acc += (u16_t)~(seg->chksum);
+ seg->tcphdr->chksum = FOLD_U32T(acc);
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+ if (chksum_slow != seg->tcphdr->chksum) {
+ LWIP_DEBUGF(TCP_DEBUG | LWIP_DBG_LEVEL_WARNING,
+ ("tcp_output_segment: calculated checksum is %"X16_F" instead of %"X16_F"\n",
+ seg->tcphdr->chksum, chksum_slow));
+ seg->tcphdr->chksum = chksum_slow;
+ }
+#endif /* TCP_CHECKSUM_ON_COPY_SANITY_CHECK */
+ }
+#else /* TCP_CHECKSUM_ON_COPY */
+ seg->tcphdr->chksum = inet_chksum_pseudo(seg->p, &(pcb->local_ip),
+ &(pcb->remote_ip),
+ IP_PROTO_TCP, seg->p->tot_len);
+#endif /* TCP_CHECKSUM_ON_COPY */
+#endif /* CHECKSUM_GEN_TCP */
TCP_STATS_INC(tcp.xmit);
#if LWIP_NETIF_HWADDRHINT
@@ -768,7 +1185,7 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
*/
void
tcp_rst(u32_t seqno, u32_t ackno,
- struct ip_addr *local_ip, struct ip_addr *remote_ip,
+ ip_addr_t *local_ip, ip_addr_t *remote_ip,
u16_t local_port, u16_t remote_port)
{
struct pbuf *p;
@@ -781,17 +1198,16 @@ tcp_rst(u32_t seqno, u32_t ackno,
LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
(p->len >= sizeof(struct tcp_hdr)));
- tcphdr = p->payload;
+ tcphdr = (struct tcp_hdr *)p->payload;
tcphdr->src = htons(local_port);
tcphdr->dest = htons(remote_port);
tcphdr->seqno = htonl(seqno);
tcphdr->ackno = htonl(ackno);
- TCPH_FLAGS_SET(tcphdr, TCP_RST | TCP_ACK);
- tcphdr->wnd = htons(TCP_WND);
+ TCPH_HDRLEN_FLAGS_SET(tcphdr, TCP_HLEN/4, TCP_RST | TCP_ACK);
+ tcphdr->wnd = PP_HTONS(TCP_WND);
+ tcphdr->chksum = 0;
tcphdr->urgp = 0;
- TCPH_HDRLEN_SET(tcphdr, 5);
- tcphdr->chksum = 0;
#if CHECKSUM_GEN_TCP
tcphdr->chksum = inet_chksum_pseudo(p, local_ip, remote_ip,
IP_PROTO_TCP, p->tot_len);
@@ -900,10 +1316,11 @@ tcp_rexmit_fast(struct tcp_pcb *pcb)
/* Set ssthresh to half of the minimum of the current
* cwnd and the advertised window */
- if (pcb->cwnd > pcb->snd_wnd)
+ if (pcb->cwnd > pcb->snd_wnd) {
pcb->ssthresh = pcb->snd_wnd / 2;
- else
+ } else {
pcb->ssthresh = pcb->cwnd / 2;
+ }
/* The minimum value for ssthresh should be 2 MSS */
if (pcb->ssthresh < 2*pcb->mss) {
@@ -935,23 +1352,19 @@ tcp_keepalive(struct tcp_pcb *pcb)
struct tcp_hdr *tcphdr;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: sending KEEPALIVE probe to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
- ip4_addr1(&pcb->remote_ip), ip4_addr2(&pcb->remote_ip),
- ip4_addr3(&pcb->remote_ip), ip4_addr4(&pcb->remote_ip)));
+ ip4_addr1_16(&pcb->remote_ip), ip4_addr2_16(&pcb->remote_ip),
+ ip4_addr3_16(&pcb->remote_ip), ip4_addr4_16(&pcb->remote_ip)));
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, pcb->keep_cnt_sent));
- p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);
-
+ p = tcp_output_alloc_header(pcb, 0, 0, htonl(pcb->snd_nxt - 1));
if(p == NULL) {
LWIP_DEBUGF(TCP_DEBUG,
("tcp_keepalive: could not allocate memory for pbuf\n"));
return;
}
- LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
- (p->len >= sizeof(struct tcp_hdr)));
-
- tcphdr = tcp_output_set_header(pcb, p, 0, htonl(pcb->snd_nxt - 1));
+ tcphdr = (struct tcp_hdr *)p->payload;
#if CHECKSUM_GEN_TCP
tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip,
@@ -994,8 +1407,8 @@ tcp_zero_window_probe(struct tcp_pcb *pcb)
LWIP_DEBUGF(TCP_DEBUG,
("tcp_zero_window_probe: sending ZERO WINDOW probe to %"
U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
- ip4_addr1(&pcb->remote_ip), ip4_addr2(&pcb->remote_ip),
- ip4_addr3(&pcb->remote_ip), ip4_addr4(&pcb->remote_ip)));
+ ip4_addr1_16(&pcb->remote_ip), ip4_addr2_16(&pcb->remote_ip),
+ ip4_addr3_16(&pcb->remote_ip), ip4_addr4_16(&pcb->remote_ip)));
LWIP_DEBUGF(TCP_DEBUG,
("tcp_zero_window_probe: tcp_ticks %"U32_F
@@ -1004,31 +1417,32 @@ tcp_zero_window_probe(struct tcp_pcb *pcb)
seg = pcb->unacked;
- if(seg == NULL)
+ if(seg == NULL) {
seg = pcb->unsent;
-
- if(seg == NULL)
+ }
+ if(seg == NULL) {
return;
+ }
is_fin = ((TCPH_FLAGS(seg->tcphdr) & TCP_FIN) != 0) && (seg->len == 0);
- len = is_fin ? TCP_HLEN : TCP_HLEN + 1;
+ /* we want to send one seqno: either FIN or data (no options) */
+ len = is_fin ? 0 : 1;
- p = pbuf_alloc(PBUF_IP, len, PBUF_RAM);
+ p = tcp_output_alloc_header(pcb, 0, len, seg->tcphdr->seqno);
if(p == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: no memory for pbuf\n"));
return;
}
- LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
- (p->len >= sizeof(struct tcp_hdr)));
-
- tcphdr = tcp_output_set_header(pcb, p, 0, seg->tcphdr->seqno);
+ tcphdr = (struct tcp_hdr *)p->payload;
if (is_fin) {
/* FIN segment, no data */
TCPH_FLAGS_SET(tcphdr, TCP_ACK | TCP_FIN);
} else {
/* Data segment, copy in one byte from the head of the unacked queue */
- *((char *)p->payload + sizeof(struct tcp_hdr)) = *(char *)seg->dataptr;
+ struct tcp_hdr *thdr = (struct tcp_hdr *)seg->p->payload;
+ char *d = ((char *)p->payload + TCP_HLEN);
+ pbuf_copy_partial(seg->p, d, 1, TCPH_HDRLEN(thdr) * 4);
}
#if CHECKSUM_GEN_TCP