diff --git a/net/tcp/Kconfig b/net/tcp/Kconfig index a9958d935c..64208e76b2 100644 --- a/net/tcp/Kconfig +++ b/net/tcp/Kconfig @@ -135,6 +135,22 @@ config NET_TCP_WINDOW_SCALE_FACTOR endif # NET_TCP_WINDOW_SCALE +config NET_TCP_OUT_OF_ORDER + bool "Enable TCP/IP Out Of Order segments" + default n + ---help--- + TCP will queue segments that arrive out of order. + +if NET_TCP_OUT_OF_ORDER + +config NET_TCP_OUT_OF_ORDER_BUFSIZE + int "TCP/IP Out Of Order buffer size" + default 16384 + ---help--- + This is the default value for out-of-order buffer size. + +endif # NET_TCP_OUT_OF_ORDER + config NET_TCP_NOTIFIER bool "Support TCP notifications" default n diff --git a/net/tcp/tcp.h b/net/tcp/tcp.h index 5637106499..bac6bc94de 100644 --- a/net/tcp/tcp.h +++ b/net/tcp/tcp.h @@ -106,6 +106,10 @@ #define TCP_WSCALE 0x01U /* Window Scale option enabled */ +/* The Max Range count of TCP Selective ACKs */ + +#define TCP_SACK_RANGES_MAX 4 + /* After receiving 3 duplicate ACKs, TCP performs a retransmission * (RFC 5681 (3.2)) */ @@ -144,6 +148,15 @@ struct tcp_poll_s FAR struct devif_callback_s *cb; /* Needed to teardown the poll */ }; +/* Out-of-order segments */ + +struct tcp_ofoseg_s +{ + uint32_t left; /* Left edge of segment */ + uint32_t right; /* Right edge of segment */ + FAR struct iob_s *data; /* Out-of-order buffering */ +}; + struct tcp_conn_s { /* Common prologue of all connection structures. */ @@ -251,6 +264,17 @@ struct tcp_conn_s struct iob_s *readahead; /* Read-ahead buffering */ +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + + /* Number of out-of-order segments */ + + uint8_t nofosegs; + + /* This defines a out of order segment block. */ + + struct tcp_ofoseg_s ofosegs[TCP_SACK_RANGES_MAX]; +#endif + #ifdef CONFIG_NET_TCP_WRITE_BUFFERS /* Write buffering * @@ -2100,6 +2124,25 @@ void tcp_sendbuffer_notify(FAR struct tcp_conn_s *conn); uint16_t tcpip_hdrsize(FAR struct tcp_conn_s *conn); +/**************************************************************************** + * Name: tcp_ofoseg_bufsize + * + * Description: + * Calculate the pending size of out-of-order buffer + * + * Input Parameters: + * conn - The TCP connection of interest + * + * Returned Value: + * Total size of out-of-order buffer + * + * Assumptions: + * This function must be called with the network locked. + * + ****************************************************************************/ + +int tcp_ofoseg_bufsize(FAR struct tcp_conn_s *conn); + #ifdef __cplusplus } #endif diff --git a/net/tcp/tcp_callback.c b/net/tcp/tcp_callback.c index 3d00cdcc1f..d825ba07eb 100644 --- a/net/tcp/tcp_callback.c +++ b/net/tcp/tcp_callback.c @@ -94,10 +94,155 @@ tcp_data_event(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, return flags; } +/**************************************************************************** + * Name: tcp_ofoseg_data_event + * + * Description: + * Handle out-of-order segment to readahead poll. + * + * Assumptions: + * - This function must be called with the network locked. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER +static uint16_t tcp_ofoseg_data_event(FAR struct net_driver_s *dev, + FAR struct tcp_conn_s *conn, + uint16_t flags) +{ + FAR struct tcp_ofoseg_s *seg; + uint32_t rcvseq; + int i = 0; + + /* Assume that we will ACK the data. The data will be ACKed if it is + * placed in the read-ahead buffer -OR- if it zero length + */ + + flags |= TCP_SNDACK; + + /* Get the receive sequence number */ + + rcvseq = tcp_getsequence(conn->rcvseq); + + ninfo("TCP OFOSEG rcvseq [%" PRIu32 "]\n", rcvseq); + + /* Foreach out-of-order segments */ + + while (i < conn->nofosegs) + { + seg = &conn->ofosegs[i]; + + /* rcvseq -->| + * ofoseg |------| + */ + + if (rcvseq == seg->left) + { + ninfo("TCP OFOSEG input [%" PRIu32 " : %" PRIu32 " : %u]\n", + seg->left, seg->right, seg->data->io_pktlen); + rcvseq = TCP_SEQ_ADD(rcvseq, + seg->data->io_pktlen); + net_incr32(conn->rcvseq, seg->data->io_pktlen); + tcp_dataconcat(&conn->readahead, &seg->data); + } + else if (TCP_SEQ_GT(rcvseq, seg->left)) + { + /* rcvseq -->| + * ofoseg |------| + */ + + if (TCP_SEQ_GTE(rcvseq, seg->right)) + { + /* Remove stale segments */ + + iob_free_chain(seg->data); + seg->data = NULL; + } + + /* rcvseq -->| + * ofoseg |------| + */ + + else + { + seg->data = + iob_trimhead(seg->data, + TCP_SEQ_SUB(rcvseq, seg->left)); + seg->left = rcvseq; + if (seg->data != NULL) + { + ninfo("TCP OFOSEG input " + "[%" PRIu32 " : %" PRIu32 " : %u]\n", + seg->left, seg->right, seg->data->io_pktlen); + rcvseq = TCP_SEQ_ADD(rcvseq, + seg->data->io_pktlen); + net_incr32(conn->rcvseq, seg->data->io_pktlen); + tcp_dataconcat(&conn->readahead, &seg->data); + } + } + } + + /* Rebuild out-of-order pool if segment is consumed */ + + if (seg->data == NULL) + { + for (; i < conn->nofosegs - 1; i++) + { + conn->ofosegs[i] = conn->ofosegs[i + 1]; + } + + conn->nofosegs--; + + /* Try segments again */ + + i = 0; + } + else + { + i++; + } + } + + return flags; +} +#endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ + /**************************************************************************** * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: tcp_ofoseg_bufsize + * + * Description: + * Calculate the pending size of out-of-order buffer + * + * Input Parameters: + * conn - The TCP connection of interest + * + * Returned Value: + * Total size of out-of-order buffer + * + * Assumptions: + * This function must be called with the network locked. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER +int tcp_ofoseg_bufsize(FAR struct tcp_conn_s *conn) +{ + int total = 0; + int i; + + for (i = 0; i < conn->nofosegs; i++) + { + total += conn->ofosegs[i].data->io_pktlen; + } + + return total; +} +#endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ + /**************************************************************************** * Name: tcp_callback * @@ -112,7 +257,7 @@ tcp_data_event(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, uint16_t tcp_callback(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, uint16_t flags) { -#ifdef CONFIG_NET_TCP_NOTIFIER +#if defined(CONFIG_NET_TCP_NOTIFIER) || defined(CONFIG_NET_TCP_OUT_OF_ORDER) uint16_t orig = flags; #endif @@ -166,6 +311,15 @@ uint16_t tcp_callback(FAR struct net_driver_s *dev, flags = tcp_data_event(dev, conn, flags); } +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + if ((orig & TCP_NEWDATA) != 0 && conn->nofosegs > 0) + { + /* Try out-of-order pool if new data is coming */ + + flags = tcp_ofoseg_data_event(dev, conn, flags); + } +#endif + /* Check if there is a connection-related event and a connection * callback. */ diff --git a/net/tcp/tcp_conn.c b/net/tcp/tcp_conn.c index de09d72c13..1c2b359478 100644 --- a/net/tcp/tcp_conn.c +++ b/net/tcp/tcp_conn.c @@ -806,6 +806,22 @@ void tcp_free(FAR struct tcp_conn_s *conn) iob_free_chain(conn->readahead); conn->readahead = NULL; +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + /* Release any out-of-order buffers */ + + if (conn->nofosegs > 0) + { + int i; + + for (i = 0; i < conn->nofosegs; i++) + { + iob_free_chain(conn->ofosegs[i].data); + } + + conn->nofosegs = 0; + } +#endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ + #ifdef CONFIG_NET_TCP_WRITE_BUFFERS /* Release any write buffers attached to the connection */ diff --git a/net/tcp/tcp_input.c b/net/tcp/tcp_input.c index 4ed4edc025..552f6b1d37 100644 --- a/net/tcp/tcp_input.c +++ b/net/tcp/tcp_input.c @@ -257,6 +257,313 @@ static void tcp_snd_wnd_update(FAR struct tcp_conn_s *conn, } } +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + +/**************************************************************************** + * Name: tcp_rebuild_ofosegs + * + * Description: + * Re-build out-of-order pool from incoming segment + * + * Input Parameters: + * conn - The TCP connection of interest + * ofoseg - Pointer to incoming out-of-order segment + * start - Index of start postion of segment pool + * + * Returned Value: + * True if incoming data has been consumed + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +static bool tcp_rebuild_ofosegs(FAR struct tcp_conn_s *conn, + FAR struct tcp_ofoseg_s *ofoseg, + int start) +{ + struct tcp_ofoseg_s *seg; + int i; + + for (i = start; i < conn->nofosegs && ofoseg->data != NULL; i++) + { + seg = &conn->ofosegs[i]; + + /* ofoseg |~~~ + * segpool |---| + */ + + if (TCP_SEQ_GTE(ofoseg->left, seg->left)) + { + /* ofoseg |---| + * segpool |---| + */ + + if (TCP_SEQ_GT(ofoseg->left, seg->right)) + { + continue; + } + + /* ofoseg |---| + * segpool |---| + */ + + else if (ofoseg->left == seg->right) + { + tcp_dataconcat(&seg->data, &ofoseg->data); + seg->right = ofoseg->right; + } + + /* ofoseg |--| + * segpool |---| + */ + + else if (TCP_SEQ_LTE(ofoseg->right, seg->right)) + { + iob_free_chain(ofoseg->data); + ofoseg->data = NULL; + } + + /* ofoseg |---| + * segpool |---| + */ + + else if (TCP_SEQ_GT(ofoseg->right, seg->right)) + { + ofoseg->data = + iob_trimhead(ofoseg->data, + TCP_SEQ_SUB(seg->right, ofoseg->left)); + tcp_dataconcat(&seg->data, &ofoseg->data); + seg->right = ofoseg->right; + } + } + + /* ofoseg |~~~ + * segpool |---| + */ + + else + { + /* ofoseg |---| + * segpool |---| + */ + + if (ofoseg->right == seg->left) + { + tcp_dataconcat(&ofoseg->data, &seg->data); + seg->data = ofoseg->data; + seg->left = ofoseg->left; + ofoseg->data = NULL; + } + + /* ofoseg |---| + * segpool |---| + */ + + else if (TCP_SEQ_LT(ofoseg->right, seg->left)) + { + continue; + } + + /* ofoseg |---|~| + * segpool |--| + */ + + else if (TCP_SEQ_GTE(ofoseg->right, seg->right)) + { + iob_free_chain(seg->data); + *seg = *ofoseg; + ofoseg->data = NULL; + } + + /* ofoseg |---| + * segpool |---| + */ + + else if (TCP_SEQ_GT(ofoseg->right, seg->left)) + { + ofoseg->data = + iob_trimtail(ofoseg->data, + ofoseg->right - seg->left); + tcp_dataconcat(&ofoseg->data, &seg->data); + seg->data = ofoseg->data; + seg->left = ofoseg->left; + ofoseg->data = NULL; + } + } + } + + return (ofoseg->data == NULL); +} + +/**************************************************************************** + * Name: tcp_reorder_ofosegs + * + * Description: + * Sort out-of-order segments by left edge + * + * Input Parameters: + * nofosegs - Number of out-of-order semgnets + * ofosegs - Pointer to out-of-order segments + * + * Returned Value: + * True if re-order occurs + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +static bool tcp_reorder_ofosegs(int nofosegs, + FAR struct tcp_ofoseg_s *ofosegs) +{ + struct tcp_ofoseg_s segs; + bool reordered = false; + int i; + int j; + + /* Sort out-of-order segments by left edge */ + + for (i = 0; i < nofosegs - 1; i++) + { + for (j = 0; j < nofosegs - 1 - i; j++) + { + if (TCP_SEQ_GT(ofosegs[j].left, + ofosegs[j + 1].left)) + { + segs = ofosegs[j]; + ofosegs[j] = ofosegs[j + 1]; + ofosegs[j + 1] = segs; + reordered = true; + } + } + } + + return reordered; +} + +/**************************************************************************** + * Name: tcp_input_ofosegs + * + * Description: + * Handle incoming TCP data to out-of-order pool + * + * Input Parameters: + * dev - The device driver structure containing the received TCP packet. + * conn - The TCP connection of interest + * iplen - Length of the IP header (IPv4_HDRLEN or IPv6_HDRLEN). + * + * Returned Value: + * None + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +static void tcp_input_ofosegs(FAR struct net_driver_s *dev, + FAR struct tcp_conn_s *conn, + unsigned int iplen) +{ + struct tcp_ofoseg_s ofoseg; + bool rebuild; + int i = 0; + int len; + + ofoseg.left = + tcp_getsequence(((FAR struct tcp_hdr_s *)IPBUF(iplen))->seqno); + + /* Calculate the pending size of out-of-order cache, if the input edge can + * not fill the adjacent segments, drop it + */ + + if (tcp_ofoseg_bufsize(conn) > CONFIG_NET_TCP_OUT_OF_ORDER_BUFSIZE && + ofoseg.left >= conn->ofosegs[0].left) + { + return; + } + + /* Get left/right edge from incoming data */ + + len = (dev->d_appdata - dev->d_iob->io_data) - dev->d_iob->io_offset; + ofoseg.right = TCP_SEQ_ADD(ofoseg.left, dev->d_iob->io_pktlen - len); + + ninfo("TCP OFOSEG out-of-order " + "[%" PRIu32 " : %" PRIu32 " : %" PRIu32 "]\n", + ofoseg.left, ofoseg.right, TCP_SEQ_SUB(ofoseg.right, ofoseg.left)); + + /* Trim l3/l4 header to reserve appdata */ + + dev->d_iob = iob_trimhead(dev->d_iob, len); + if (dev->d_iob == NULL) + { + /* No available data, clear device buffer */ + + goto clear; + } + + ofoseg.data = dev->d_iob; + + /* Build out-of-order pool */ + + rebuild = tcp_rebuild_ofosegs(conn, &ofoseg, 0); + + /* Incoming segment out of order from existing pool, add to new segment */ + + if (!rebuild && conn->nofosegs != TCP_SACK_RANGES_MAX) + { + conn->ofosegs[conn->nofosegs] = ofoseg; + conn->nofosegs++; + rebuild = true; + } + + /* Try Re-order ofosegs */ + + if (rebuild && + tcp_reorder_ofosegs(conn->nofosegs, (FAR void *)conn->ofosegs)) + { + /* Re-build out-of-order pool after re-order */ + + while (i < conn->nofosegs - 1) + { + if (tcp_rebuild_ofosegs(conn, &conn->ofosegs[i], i + 1)) + { + for (; i < conn->nofosegs - 1; i++) + { + conn->ofosegs[i] = conn->ofosegs[i + 1]; + } + + conn->nofosegs--; + + i = 0; + } + else + { + i++; + } + } + } + + for (i = 0; i < conn->nofosegs; i++) + { + ninfo("TCP OFOSEG [%d][%" PRIu32 " : %" PRIu32 " : %" PRIu32 "]\n", i, + conn->ofosegs[i].left, conn->ofosegs[i].right, + TCP_SEQ_SUB(conn->ofosegs[i].right, conn->ofosegs[i].left)); + } + + /* Incoming data has been consumed, re-prepare device buffer to send + * response. + */ + + if (rebuild) + { +clear: + netdev_iob_clear(dev); + netdev_iob_prepare(dev, false, 0); + } +} +#endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ + /**************************************************************************** * Name: tcp_input * @@ -697,8 +1004,11 @@ found: } else { - /* We never queue out-of-order segments. */ +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + /* Queue out-of-order segments. */ + tcp_input_ofosegs(dev, conn, iplen); +#endif tcp_send(dev, conn, TCP_ACK, tcpiplen); return; } diff --git a/net/tcp/tcp_recvwindow.c b/net/tcp/tcp_recvwindow.c index 7be6fd1682..2777c86230 100644 --- a/net/tcp/tcp_recvwindow.c +++ b/net/tcp/tcp_recvwindow.c @@ -219,6 +219,36 @@ uint32_t tcp_get_recvwindow(FAR struct net_driver_s *dev, recvwndo = tcp_calc_rcvsize(conn, recvwndo); +#ifdef CONFIG_NET_TCP_OUT_OF_ORDER + /* Calculate the minimum desired size */ + + if (conn->nofosegs > 0) + { + uint32_t desire = conn->ofosegs[0].left - + tcp_getsequence(conn->rcvseq); + int bufsize = tcp_ofoseg_bufsize(conn); + + if (desire < tcp_rx_mss(dev)) + { + desire = tcp_rx_mss(dev); + } + + if (TCP_SEQ_LT(recvwndo, bufsize)) + { + recvwndo = 0; + } + else + { + recvwndo -= bufsize; + } + + if (recvwndo < desire) + { + recvwndo = desire; + } + } +#endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ + #ifdef CONFIG_NET_TCP_WINDOW_SCALE recvwndo >>= conn->rcv_scale; #endif