summaryrefslogtreecommitdiffstats
path: root/NetworkPkg/TcpDxe/TcpOutput.c
diff options
context:
space:
mode:
authorhhtian <hhtian@6f19259b-4bc3-4df7-8a09-765794883524>2010-11-01 06:13:54 +0000
committerhhtian <hhtian@6f19259b-4bc3-4df7-8a09-765794883524>2010-11-01 06:13:54 +0000
commita3bcde70e6dc69000f85cc5deee98101d2ae200a (patch)
tree693709a5293f80b320931693b34b2d6983cfcf4b /NetworkPkg/TcpDxe/TcpOutput.c
parent12873d57666d0beff41959a1fb8f9062016f0983 (diff)
downloadedk2-a3bcde70e6dc69000f85cc5deee98101d2ae200a.tar.gz
edk2-a3bcde70e6dc69000f85cc5deee98101d2ae200a.tar.bz2
edk2-a3bcde70e6dc69000f85cc5deee98101d2ae200a.zip
Add NetworkPkg (P.UDK2010.UP3.Network.P1)
git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@10986 6f19259b-4bc3-4df7-8a09-765794883524
Diffstat (limited to 'NetworkPkg/TcpDxe/TcpOutput.c')
-rw-r--r--NetworkPkg/TcpDxe/TcpOutput.c1219
1 files changed, 1219 insertions, 0 deletions
diff --git a/NetworkPkg/TcpDxe/TcpOutput.c b/NetworkPkg/TcpDxe/TcpOutput.c
new file mode 100644
index 0000000000..c038213484
--- /dev/null
+++ b/NetworkPkg/TcpDxe/TcpOutput.c
@@ -0,0 +1,1219 @@
+/** @file
+ TCP output process routines.
+
+ Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>
+
+ This program and the accompanying materials
+ are licensed and made available under the terms and conditions of the BSD License
+ which accompanies this distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php.
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include "TcpMain.h"
+
+UINT8 mTcpOutFlag[] = {
+ 0, // TCP_CLOSED
+ 0, // TCP_LISTEN
+ TCP_FLG_SYN, // TCP_SYN_SENT
+ TCP_FLG_SYN | TCP_FLG_ACK, // TCP_SYN_RCVD
+ TCP_FLG_ACK, // TCP_ESTABLISHED
+ TCP_FLG_FIN | TCP_FLG_ACK, // TCP_FIN_WAIT_1
+ TCP_FLG_ACK, // TCP_FIN_WAIT_2
+ TCP_FLG_ACK | TCP_FLG_FIN, // TCP_CLOSING
+ TCP_FLG_ACK, // TCP_TIME_WAIT
+ TCP_FLG_ACK, // TCP_CLOSE_WAIT
+ TCP_FLG_FIN | TCP_FLG_ACK // TCP_LAST_ACK
+};
+
+/**
+ Compute the sequence space left in the old receive window.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+
+ @return The sequence space left in the old receive window.
+
+**/
+UINT32
+TcpRcvWinOld (
+ IN TCP_CB *Tcb
+ )
+{
+ UINT32 OldWin;
+
+ OldWin = 0;
+
+ if (TCP_SEQ_GT (Tcb->RcvWl2 + Tcb->RcvWnd, Tcb->RcvNxt)) {
+
+ OldWin = TCP_SUB_SEQ (
+ Tcb->RcvWl2 + Tcb->RcvWnd,
+ Tcb->RcvNxt
+ );
+ }
+
+ return OldWin;
+}
+
+/**
+ Compute the current receive window.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+
+ @return The size of the current receive window, in bytes.
+
+**/
+UINT32
+TcpRcvWinNow (
+ IN TCP_CB *Tcb
+ )
+{
+ SOCKET *Sk;
+ UINT32 Win;
+ UINT32 Increase;
+ UINT32 OldWin;
+
+ Sk = Tcb->Sk;
+ ASSERT (Sk != NULL);
+
+ OldWin = TcpRcvWinOld (Tcb);
+
+ Win = SockGetFreeSpace (Sk, SOCK_RCV_BUF);
+
+ Increase = 0;
+ if (Win > OldWin) {
+ Increase = Win - OldWin;
+ }
+
+ //
+ // Receiver's SWS: don't advertise a bigger window
+ // unless it can be increased by at least one Mss or
+ // half of the receive buffer.
+ //
+ if ((Increase > Tcb->SndMss) || (2 * Increase >= GET_RCV_BUFFSIZE (Sk))) {
+
+ return Win;
+ }
+
+ return OldWin;
+}
+
+/**
+ Compute the value to fill in the window size field of the outgoing segment.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Syn The flag to indicate whether the outgoing segment
+ is a SYN segment.
+
+ @return The value of the local receive window size used to fill the outgoing segment.
+
+**/
+UINT16
+TcpComputeWnd (
+ IN OUT TCP_CB *Tcb,
+ IN BOOLEAN Syn
+ )
+{
+ UINT32 Wnd;
+
+ //
+ // RFC requires that initial window not be scaled
+ //
+ if (Syn) {
+
+ Wnd = GET_RCV_BUFFSIZE (Tcb->Sk);
+ } else {
+
+ Wnd = TcpRcvWinNow (Tcb);
+
+ Tcb->RcvWnd = Wnd;
+ }
+
+ Wnd = MIN (Wnd >> Tcb->RcvWndScale, 0xffff);
+ return NTOHS ((UINT16) Wnd);
+}
+
+/**
+ Get the maximum SndNxt.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+
+ @return The sequence number of the maximum SndNxt.
+
+**/
+TCP_SEQNO
+TcpGetMaxSndNxt (
+ IN TCP_CB *Tcb
+ )
+{
+ LIST_ENTRY *Entry;
+ NET_BUF *Nbuf;
+
+ if (IsListEmpty (&Tcb->SndQue)) {
+ return Tcb->SndNxt;
+ }
+
+ Entry = Tcb->SndQue.BackLink;
+ Nbuf = NET_LIST_USER_STRUCT (Entry, NET_BUF, List);
+
+ ASSERT (TCP_SEQ_GEQ (TCPSEG_NETBUF (Nbuf)->End, Tcb->SndNxt));
+ return TCPSEG_NETBUF (Nbuf)->End;
+}
+
+/**
+ Compute how much data to send.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Force If TRUE, to ignore the sender's SWS avoidance algorithm and send
+ out data by force.
+
+ @return The length of the data can be sent. If 0, no data can be sent.
+
+**/
+UINT32
+TcpDataToSend (
+ IN TCP_CB *Tcb,
+ IN INTN Force
+ )
+{
+ SOCKET *Sk;
+ UINT32 Win;
+ UINT32 Len;
+ UINT32 Left;
+ UINT32 Limit;
+
+ Sk = Tcb->Sk;
+ ASSERT (Sk != NULL);
+
+ //
+ // TCP should NOT send data beyond the send window
+ // and congestion window. The right edge of send
+ // window is defined as SND.WL2 + SND.WND. The right
+ // edge of congestion window is defined as SND.UNA +
+ // CWND.
+ //
+ Win = 0;
+ Limit = Tcb->SndWl2 + Tcb->SndWnd;
+
+ if (TCP_SEQ_GT (Limit, Tcb->SndUna + Tcb->CWnd)) {
+
+ Limit = Tcb->SndUna + Tcb->CWnd;
+ }
+
+ if (TCP_SEQ_GT (Limit, Tcb->SndNxt)) {
+ Win = TCP_SUB_SEQ (Limit, Tcb->SndNxt);
+ }
+
+ //
+ // The data to send contains two parts: the data on the
+ // socket send queue, and the data on the TCB's send
+ // buffer. The later can be non-zero if the peer shrinks
+ // its advertised window.
+ //
+ Left = GET_SND_DATASIZE (Sk) + TCP_SUB_SEQ (TcpGetMaxSndNxt (Tcb), Tcb->SndNxt);
+
+ Len = MIN (Win, Left);
+
+ if (Len > Tcb->SndMss) {
+ Len = Tcb->SndMss;
+ }
+
+ if ((Force != 0)|| (Len == 0 && Left == 0)) {
+ return Len;
+ }
+
+ if (Len == 0 && Left != 0) {
+ goto SetPersistTimer;
+ }
+
+ //
+ // Sender's SWS avoidance: Don't send a small segment unless
+ // a)A full-sized segment can be sent,
+ // b)At least one-half of the maximum sized windows that
+ // the other end has ever advertised.
+ // c)It can send everything it has, and either it isn't
+ // expecting an ACK, or the Nagle algorithm is disabled.
+ //
+ if ((Len == Tcb->SndMss) || (2 * Len >= Tcb->SndWndMax)) {
+
+ return Len;
+ }
+
+ if ((Len == Left) &&
+ ((Tcb->SndNxt == Tcb->SndUna) || TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE))
+ ) {
+
+ return Len;
+ }
+
+ //
+ // RFC1122 suggests to set a timer when SWSA forbids TCP
+ // sending more data, and combines it with a probe timer.
+ //
+SetPersistTimer:
+ if (!TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_REXMIT)) {
+
+ DEBUG (
+ (EFI_D_WARN,
+ "TcpDataToSend: enter persistent state for TCB %p\n",
+ Tcb)
+ );
+
+ if (!Tcb->ProbeTimerOn) {
+ TcpSetProbeTimer (Tcb);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ Build the TCP header of the TCP segment and transmit the segment by IP.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Nbuf Pointer to the buffer containing the segment to be
+ sent out.
+
+ @retval 0 The segment was sent out successfully.
+ @retval -1 An error condition occurred.
+
+**/
+INTN
+TcpTransmitSegment (
+ IN OUT TCP_CB *Tcb,
+ IN NET_BUF *Nbuf
+ )
+{
+ UINT16 Len;
+ TCP_HEAD *Head;
+ TCP_SEG *Seg;
+ BOOLEAN Syn;
+ UINT32 DataLen;
+
+ ASSERT ((Nbuf != NULL) && (Nbuf->Tcp == NULL) && (TcpVerifySegment (Nbuf) != 0));
+
+ DataLen = Nbuf->TotalSize;
+
+ Seg = TCPSEG_NETBUF (Nbuf);
+ Syn = TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN);
+
+ if (Syn) {
+
+ Len = TcpSynBuildOption (Tcb, Nbuf);
+ } else {
+
+ Len = TcpBuildOption (Tcb, Nbuf);
+ }
+
+ ASSERT ((Len % 4 == 0) && (Len <= 40));
+
+ Len += sizeof (TCP_HEAD);
+
+ Head = (TCP_HEAD *) NetbufAllocSpace (
+ Nbuf,
+ sizeof (TCP_HEAD),
+ NET_BUF_HEAD
+ );
+
+ ASSERT (Head != NULL);
+
+ Nbuf->Tcp = Head;
+
+ Head->SrcPort = Tcb->LocalEnd.Port;
+ Head->DstPort = Tcb->RemoteEnd.Port;
+ Head->Seq = NTOHL (Seg->Seq);
+ Head->Ack = NTOHL (Tcb->RcvNxt);
+ Head->HeadLen = (UINT8) (Len >> 2);
+ Head->Res = 0;
+ Head->Wnd = TcpComputeWnd (Tcb, Syn);
+ Head->Checksum = 0;
+
+ //
+ // Check whether to set the PSH flag.
+ //
+ TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_PSH);
+
+ if (DataLen != 0) {
+ if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_SND_PSH) &&
+ TCP_SEQ_BETWEEN (Seg->Seq, Tcb->SndPsh, Seg->End)
+ ) {
+
+ TCP_SET_FLG (Seg->Flag, TCP_FLG_PSH);
+ TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_PSH);
+
+ } else if ((Seg->End == Tcb->SndNxt) && (GET_SND_DATASIZE (Tcb->Sk) == 0)) {
+
+ TCP_SET_FLG (Seg->Flag, TCP_FLG_PSH);
+ }
+ }
+
+ //
+ // Check whether to set the URG flag and the urgent pointer.
+ //
+ TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_URG);
+
+ if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_SND_URG) && TCP_SEQ_LEQ (Seg->Seq, Tcb->SndUp)) {
+
+ TCP_SET_FLG (Seg->Flag, TCP_FLG_URG);
+
+ if (TCP_SEQ_LT (Tcb->SndUp, Seg->End)) {
+
+ Seg->Urg = (UINT16) TCP_SUB_SEQ (Tcb->SndUp, Seg->Seq);
+ } else {
+
+ Seg->Urg = (UINT16) MIN (
+ TCP_SUB_SEQ (Tcb->SndUp,
+ Seg->Seq),
+ 0xffff
+ );
+ }
+ }
+
+ Head->Flag = Seg->Flag;
+ Head->Urg = NTOHS (Seg->Urg);
+ Head->Checksum = TcpChecksum (Nbuf, Tcb->HeadSum);
+
+ //
+ // Update the TCP session's control information.
+ //
+ Tcb->RcvWl2 = Tcb->RcvNxt;
+ if (Syn) {
+ Tcb->RcvWnd = NTOHS (Head->Wnd);
+ }
+
+ //
+ // Clear the delayedack flag.
+ //
+ Tcb->DelayedAck = 0;
+
+ return TcpSendIpPacket (Tcb, Nbuf, &Tcb->LocalEnd.Ip, &Tcb->RemoteEnd.Ip, Tcb->Sk->IpVersion);
+}
+
+/**
+ Get a segment from the Tcb's SndQue.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Seq The sequence number of the segment.
+ @param[in] Len The maximum length of the segment.
+
+ @return Pointer to the segment. If NULL, some error occurred.
+
+**/
+NET_BUF *
+TcpGetSegmentSndQue (
+ IN TCP_CB *Tcb,
+ IN TCP_SEQNO Seq,
+ IN UINT32 Len
+ )
+{
+ LIST_ENTRY *Head;
+ LIST_ENTRY *Cur;
+ NET_BUF *Node;
+ TCP_SEG *Seg;
+ NET_BUF *Nbuf;
+ TCP_SEQNO End;
+ UINT8 *Data;
+ UINT8 Flag;
+ INT32 Offset;
+ INT32 CopyLen;
+
+ ASSERT ((Tcb != NULL) && TCP_SEQ_LEQ (Seq, Tcb->SndNxt) && (Len > 0));
+
+ //
+ // Find the segment that contains the Seq.
+ //
+ Head = &Tcb->SndQue;
+
+ Node = NULL;
+ Seg = NULL;
+
+ NET_LIST_FOR_EACH (Cur, Head) {
+ Node = NET_LIST_USER_STRUCT (Cur, NET_BUF, List);
+ Seg = TCPSEG_NETBUF (Node);
+
+ if (TCP_SEQ_LT (Seq, Seg->End) && TCP_SEQ_LEQ (Seg->Seq, Seq)) {
+
+ break;
+ }
+ }
+
+ if ((Cur == Head) || (Seg == NULL) || (Node == NULL)) {
+ return NULL;
+ }
+
+ //
+ // Return the buffer if it can be returned without
+ // adjustment:
+ //
+ if ((Seg->Seq == Seq) &&
+ TCP_SEQ_LEQ (Seg->End, Seg->Seq + Len) &&
+ !NET_BUF_SHARED (Node)
+ ) {
+
+ NET_GET_REF (Node);
+ return Node;
+ }
+
+ //
+ // Create a new buffer and copy data there.
+ //
+ Nbuf = NetbufAlloc (Len + TCP_MAX_HEAD);
+
+ if (Nbuf == NULL) {
+ return NULL;
+ }
+
+ NetbufReserve (Nbuf, TCP_MAX_HEAD);
+
+ Flag = Seg->Flag;
+ End = Seg->End;
+
+ if (TCP_SEQ_LT (Seq + Len, Seg->End)) {
+ End = Seq + Len;
+ }
+
+ CopyLen = TCP_SUB_SEQ (End, Seq);
+ Offset = TCP_SUB_SEQ (Seq, Seg->Seq);
+
+ //
+ // If SYN is set and out of the range, clear the flag.
+ // Becuase the sequence of the first byte is SEG.SEQ+1,
+ // adjust Offset by -1. If SYN is in the range, copy
+ // one byte less.
+ //
+ if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
+
+ if (TCP_SEQ_LT (Seg->Seq, Seq)) {
+
+ TCP_CLEAR_FLG (Flag, TCP_FLG_SYN);
+ Offset--;
+ } else {
+
+ CopyLen--;
+ }
+ }
+
+ //
+ // If FIN is set and in the range, copy one byte less,
+ // and if it is out of the range, clear the flag.
+ //
+ if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
+
+ if (Seg->End == End) {
+
+ CopyLen--;
+ } else {
+
+ TCP_CLEAR_FLG (Flag, TCP_FLG_FIN);
+ }
+ }
+
+ ASSERT (CopyLen >= 0);
+
+ //
+ // Copy data to the segment
+ //
+ if (CopyLen != 0) {
+ Data = NetbufAllocSpace (Nbuf, CopyLen, NET_BUF_TAIL);
+ ASSERT (Data != NULL);
+
+ if ((INT32) NetbufCopy (Node, Offset, CopyLen, Data) != CopyLen) {
+ goto OnError;
+ }
+ }
+
+ CopyMem (TCPSEG_NETBUF (Nbuf), Seg, sizeof (TCP_SEG));
+
+ TCPSEG_NETBUF (Nbuf)->Seq = Seq;
+ TCPSEG_NETBUF (Nbuf)->End = End;
+ TCPSEG_NETBUF (Nbuf)->Flag = Flag;
+
+ return Nbuf;
+
+OnError:
+ NetbufFree (Nbuf);
+ return NULL;
+}
+
+/**
+ Get a segment from the Tcb's socket buffer.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Seq The sequence number of the segment.
+ @param[in] Len The maximum length of the segment.
+
+ @return Pointer to the segment. If NULL, some error occurred.
+
+**/
+NET_BUF *
+TcpGetSegmentSock (
+ IN TCP_CB *Tcb,
+ IN TCP_SEQNO Seq,
+ IN UINT32 Len
+ )
+{
+ NET_BUF *Nbuf;
+ UINT8 *Data;
+ UINT32 DataGet;
+
+ ASSERT ((Tcb != NULL) && (Tcb->Sk != NULL));
+
+ Nbuf = NetbufAlloc (Len + TCP_MAX_HEAD);
+
+ if (Nbuf == NULL) {
+ DEBUG (
+ (EFI_D_ERROR,
+ "TcpGetSegmentSock: failed to allocate a netbuf for TCB %p\n",
+ Tcb)
+ );
+
+ return NULL;
+ }
+
+ NetbufReserve (Nbuf, TCP_MAX_HEAD);
+
+ DataGet = 0;
+
+ if (Len != 0) {
+ //
+ // copy data to the segment.
+ //
+ Data = NetbufAllocSpace (Nbuf, Len, NET_BUF_TAIL);
+ ASSERT (Data != NULL);
+
+ DataGet = SockGetDataToSend (Tcb->Sk, 0, Len, Data);
+ }
+
+ NET_GET_REF (Nbuf);
+
+ TCPSEG_NETBUF (Nbuf)->Seq = Seq;
+ TCPSEG_NETBUF (Nbuf)->End = Seq + Len;
+
+ InsertTailList (&(Tcb->SndQue), &(Nbuf->List));
+
+ if (DataGet != 0) {
+
+ SockDataSent (Tcb->Sk, DataGet);
+ }
+
+ return Nbuf;
+}
+
+/**
+ Get a segment starting from sequence Seq of a maximum
+ length of Len.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Seq The sequence number of the segment.
+ @param[in] Len The maximum length of the segment.
+
+ @return Pointer to the segment. If NULL, some error occurred.
+
+**/
+NET_BUF *
+TcpGetSegment (
+ IN TCP_CB *Tcb,
+ IN TCP_SEQNO Seq,
+ IN UINT32 Len
+ )
+{
+ NET_BUF *Nbuf;
+
+ ASSERT (Tcb != NULL);
+
+ //
+ // Compare the SndNxt with the max sequence number sent.
+ //
+ if ((Len != 0) && TCP_SEQ_LT (Seq, TcpGetMaxSndNxt (Tcb))) {
+
+ Nbuf = TcpGetSegmentSndQue (Tcb, Seq, Len);
+ } else {
+
+ Nbuf = TcpGetSegmentSock (Tcb, Seq, Len);
+ }
+
+ ASSERT (TcpVerifySegment (Nbuf) != 0);
+ return Nbuf;
+}
+
+/**
+ Retransmit the segment from sequence Seq.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Seq The sequence number of the segment to be retransmitted.
+
+ @retval 0 Retransmission succeeded.
+ @retval -1 Error condition occurred.
+
+**/
+INTN
+TcpRetransmit (
+ IN TCP_CB *Tcb,
+ IN TCP_SEQNO Seq
+ )
+{
+ NET_BUF *Nbuf;
+ UINT32 Len;
+
+ //
+ // Compute the maxium length of retransmission. It is
+ // limited by three factors:
+ // 1. Less than SndMss
+ // 2. Must in the current send window
+ // 3. Will not change the boundaries of queued segments.
+ //
+ if (TCP_SEQ_LT (Tcb->SndWl2 + Tcb->SndWnd, Seq)) {
+ DEBUG (
+ (EFI_D_WARN,
+ "TcpRetransmit: retransmission cancelled because send window too small for TCB %p\n",
+ Tcb)
+ );
+
+ return 0;
+ }
+
+ Len = TCP_SUB_SEQ (Tcb->SndWl2 + Tcb->SndWnd, Seq);
+ Len = MIN (Len, Tcb->SndMss);
+
+ Nbuf = TcpGetSegmentSndQue (Tcb, Seq, Len);
+ if (Nbuf == NULL) {
+ return -1;
+ }
+
+ ASSERT (TcpVerifySegment (Nbuf) != 0);
+
+ if (TcpTransmitSegment (Tcb, Nbuf) != 0) {
+ goto OnError;
+ }
+
+ //
+ // The retransmitted buffer may be on the SndQue,
+ // trim TCP head because all the buffers on SndQue
+ // are headless.
+ //
+ ASSERT (Nbuf->Tcp != NULL);
+ NetbufTrim (Nbuf, (Nbuf->Tcp->HeadLen << 2), NET_BUF_HEAD);
+ Nbuf->Tcp = NULL;
+
+ NetbufFree (Nbuf);
+ return 0;
+
+OnError:
+ if (Nbuf != NULL) {
+ NetbufFree (Nbuf);
+ }
+
+ return -1;
+}
+
+/**
+ Verify that all the segments in SndQue are in good shape.
+
+ @param[in] Head Pointer to the head node of the SndQue.
+
+ @retval 0 At least one segment is broken.
+ @retval 1 All segments in the specific queue are in good shape.
+
+**/
+INTN
+TcpCheckSndQue (
+ IN LIST_ENTRY *Head
+ )
+{
+ LIST_ENTRY *Entry;
+ NET_BUF *Nbuf;
+ TCP_SEQNO Seq;
+
+ if (IsListEmpty (Head)) {
+ return 1;
+ }
+ //
+ // Initialize the Seq.
+ //
+ Entry = Head->ForwardLink;
+ Nbuf = NET_LIST_USER_STRUCT (Entry, NET_BUF, List);
+ Seq = TCPSEG_NETBUF (Nbuf)->Seq;
+
+ NET_LIST_FOR_EACH (Entry, Head) {
+ Nbuf = NET_LIST_USER_STRUCT (Entry, NET_BUF, List);
+
+ if (TcpVerifySegment (Nbuf) == 0) {
+ return 0;
+ }
+
+ //
+ // All the node in the SndQue should has:
+ // SEG.SEQ = LAST_SEG.END
+ //
+ if (Seq != TCPSEG_NETBUF (Nbuf)->Seq) {
+ return 0;
+ }
+
+ Seq = TCPSEG_NETBUF (Nbuf)->End;
+ }
+
+ return 1;
+}
+
+/**
+ Check whether to send data/SYN/FIN and piggyback an ACK.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+ @param[in] Force If TRUE, ignore the sender's SWS avoidance algorithm
+ and send out data by force.
+
+ @return The number of bytes sent.
+
+**/
+INTN
+TcpToSendData (
+ IN OUT TCP_CB *Tcb,
+ IN INTN Force
+ )
+{
+ UINT32 Len;
+ INTN Sent;
+ UINT8 Flag;
+ NET_BUF *Nbuf;
+ TCP_SEG *Seg;
+ TCP_SEQNO Seq;
+ TCP_SEQNO End;
+
+ ASSERT ((Tcb != NULL) && (Tcb->Sk != NULL) && (Tcb->State != TCP_LISTEN));
+
+ Sent = 0;
+
+ if ((Tcb->State == TCP_CLOSED) || TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_SENT)) {
+
+ return 0;
+ }
+
+ do {
+ //
+ // Compute how much data can be sent
+ //
+ Len = TcpDataToSend (Tcb, Force);
+ Seq = Tcb->SndNxt;
+
+ ASSERT ((Tcb->State) < (sizeof (mTcpOutFlag) / sizeof (mTcpOutFlag[0])));
+ Flag = mTcpOutFlag[Tcb->State];
+
+ if ((Flag & TCP_FLG_SYN) != 0) {
+
+ Seq = Tcb->Iss;
+ Len = 0;
+ }
+
+ //
+ // Only send a segment without data if SYN or
+ // FIN is set.
+ //
+ if ((Len == 0) && ((Flag & (TCP_FLG_SYN | TCP_FLG_FIN)) == 0)) {
+ return Sent;
+ }
+
+ Nbuf = TcpGetSegment (Tcb, Seq, Len);
+
+ if (Nbuf == NULL) {
+ DEBUG (
+ (EFI_D_ERROR,
+ "TcpToSendData: failed to get a segment for TCB %p\n",
+ Tcb)
+ );
+
+ goto OnError;
+ }
+
+ Seg = TCPSEG_NETBUF (Nbuf);
+
+ //
+ // Set the TcpSeg in Nbuf.
+ //
+ Len = Nbuf->TotalSize;
+ End = Seq + Len;
+ if (TCP_FLG_ON (Flag, TCP_FLG_SYN)) {
+ End++;
+ }
+
+ if ((Flag & TCP_FLG_FIN) != 0) {
+ //
+ // Send FIN if all data is sent, and FIN is
+ // in the window
+ //
+ if ((TcpGetMaxSndNxt (Tcb) == Tcb->SndNxt) &&
+ (GET_SND_DATASIZE (Tcb->Sk) == 0) &&
+ TCP_SEQ_LT (End + 1, Tcb->SndWnd + Tcb->SndWl2)
+ ) {
+ DEBUG (
+ (EFI_D_INFO,
+ "TcpToSendData: send FIN to peer for TCB %p in state %s\n",
+ Tcb,
+ mTcpStateName[Tcb->State])
+ );
+
+ End++;
+ } else {
+ TCP_CLEAR_FLG (Flag, TCP_FLG_FIN);
+ }
+ }
+
+ Seg->Seq = Seq;
+ Seg->End = End;
+ Seg->Flag = Flag;
+
+ ASSERT (TcpVerifySegment (Nbuf) != 0);
+ ASSERT (TcpCheckSndQue (&Tcb->SndQue) != 0);
+
+ //
+ // Don't send an empty segment here.
+ //
+ if (Seg->End == Seg->Seq) {
+ DEBUG (
+ (EFI_D_WARN,
+ "TcpToSendData: created a empty segment for TCB %p, free it now\n",
+ Tcb)
+ );
+
+ NetbufFree (Nbuf);
+ return Sent;
+ }
+
+ if (TcpTransmitSegment (Tcb, Nbuf) != 0) {
+ NetbufTrim (Nbuf, (Nbuf->Tcp->HeadLen << 2), NET_BUF_HEAD);
+ Nbuf->Tcp = NULL;
+
+ if ((Flag & TCP_FLG_FIN) != 0) {
+ TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_FIN_SENT);
+ }
+
+ goto OnError;
+ }
+
+ Sent += TCP_SUB_SEQ (End, Seq);
+
+ //
+ // All the buffers in the SndQue are headless.
+ //
+ ASSERT (Nbuf->Tcp != NULL);
+
+ NetbufTrim (Nbuf, (Nbuf->Tcp->HeadLen << 2), NET_BUF_HEAD);
+ Nbuf->Tcp = NULL;
+
+ NetbufFree (Nbuf);
+
+ //
+ // Update the status in TCB.
+ //
+ Tcb->DelayedAck = 0;
+
+ if ((Flag & TCP_FLG_FIN) != 0) {
+ TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_FIN_SENT);
+ }
+
+ if (TCP_SEQ_GT (End, Tcb->SndNxt)) {
+ Tcb->SndNxt = End;
+ }
+
+ if (!TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_REXMIT)) {
+ TcpSetTimer (Tcb, TCP_TIMER_REXMIT, Tcb->Rto);
+ }
+
+ //
+ // Enable RTT measurement only if not in retransmit.
+ // Karn's algorithm requires not to update RTT when in loss.
+ //
+ if ((Tcb->CongestState == TCP_CONGEST_OPEN) && !TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RTT_ON)) {
+
+ DEBUG (
+ (EFI_D_INFO,
+ "TcpToSendData: set RTT measure sequence %d for TCB %p\n",
+ Seq,
+ Tcb)
+ );
+
+ TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON);
+ Tcb->RttSeq = Seq;
+ Tcb->RttMeasure = 0;
+ }
+
+ } while (Len == Tcb->SndMss);
+
+ return Sent;
+
+OnError:
+ if (Nbuf != NULL) {
+ NetbufFree (Nbuf);
+ }
+
+ return Sent;
+}
+
+/**
+ Send an ACK immediately.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+
+**/
+VOID
+TcpSendAck (
+ IN OUT TCP_CB *Tcb
+ )
+{
+ NET_BUF *Nbuf;
+ TCP_SEG *Seg;
+
+ Nbuf = NetbufAlloc (TCP_MAX_HEAD);
+
+ if (Nbuf == NULL) {
+ return;
+ }
+
+ NetbufReserve (Nbuf, TCP_MAX_HEAD);
+
+ Seg = TCPSEG_NETBUF (Nbuf);
+ Seg->Seq = Tcb->SndNxt;
+ Seg->End = Tcb->SndNxt;
+ Seg->Flag = TCP_FLG_ACK;
+
+ if (TcpTransmitSegment (Tcb, Nbuf) == 0) {
+ TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
+ Tcb->DelayedAck = 0;
+ }
+
+ NetbufFree (Nbuf);
+}
+
+/**
+ Send a zero probe segment. It can be used by keepalive and zero window probe.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+
+ @retval 0 The zero probe segment was sent out successfully.
+ @retval other An error condition occurred.
+
+**/
+INTN
+TcpSendZeroProbe (
+ IN OUT TCP_CB *Tcb
+ )
+{
+ NET_BUF *Nbuf;
+ TCP_SEG *Seg;
+ INTN Result;
+
+ Nbuf = NetbufAlloc (TCP_MAX_HEAD);
+
+ if (Nbuf == NULL) {
+ return -1;
+ }
+
+ NetbufReserve (Nbuf, TCP_MAX_HEAD);
+
+ //
+ // SndNxt-1 is out of window. The peer should respond
+ // with an ACK.
+ //
+ Seg = TCPSEG_NETBUF (Nbuf);
+ Seg->Seq = Tcb->SndNxt - 1;
+ Seg->End = Tcb->SndNxt - 1;
+ Seg->Flag = TCP_FLG_ACK;
+
+ Result = TcpTransmitSegment (Tcb, Nbuf);
+ NetbufFree (Nbuf);
+
+ return Result;
+}
+
+/**
+ Check whether to send an ACK or delayed ACK.
+
+ @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
+
+**/
+VOID
+TcpToSendAck (
+ IN OUT TCP_CB *Tcb
+ )
+{
+ UINT32 TcpNow;
+
+ //
+ // Generally, TCP should send a delayed ACK unless:
+ // 1. ACK at least every other FULL sized segment received.
+ // 2. Packets received out of order.
+ // 3. Receiving window is open.
+ //
+ if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW) || (Tcb->DelayedAck >= 1)) {
+ TcpSendAck (Tcb);
+ return;
+ }
+
+ TcpNow = TcpRcvWinNow (Tcb);
+
+ if (TcpNow > TcpRcvWinOld (Tcb)) {
+ TcpSendAck (Tcb);
+ return;
+ }
+
+ DEBUG (
+ (EFI_D_INFO,
+ "TcpToSendAck: scheduled a delayed ACK for TCB %p\n",
+ Tcb)
+ );
+
+ //
+ // Schedule a delayed ACK.
+ //
+ Tcb->DelayedAck++;
+}
+
+/**
+ Send a RESET segment in response to the segment received.
+
+ @param[in] Tcb Pointer to the TCP_CB of this TCP instance. May be NULL.
+ @param[in] Head TCP header of the segment that triggers the reset.
+ @param[in] Len Length of the segment that triggers the reset.
+ @param[in] Local Local IP address.
+ @param[in] Remote Remote peer's IP address.
+ @param[in] Version IP_VERSION_4 indicates TCP is running on IP4 stack,
+ IP_VERSION_6 indicates TCP is running on IP6 stack.
+
+ @retval 0 A reset was sent or there is no need to send it.
+ @retval -1 No reset is sent.
+
+**/
+INTN
+TcpSendReset (
+ IN TCP_CB *Tcb,
+ IN TCP_HEAD *Head,
+ IN INT32 Len,
+ IN EFI_IP_ADDRESS *Local,
+ IN EFI_IP_ADDRESS *Remote,
+ IN UINT8 Version
+ )
+{
+ NET_BUF *Nbuf;
+ TCP_HEAD *Nhead;
+ UINT16 HeadSum;
+
+ //
+ // Don't respond to a Reset with reset.
+ //
+ if ((Head->Flag & TCP_FLG_RST) != 0) {
+ return 0;
+ }
+
+ Nbuf = NetbufAlloc (TCP_MAX_HEAD);
+
+ if (Nbuf == NULL) {
+ return -1;
+ }
+
+ Nhead = (TCP_HEAD *) NetbufAllocSpace (
+ Nbuf,
+ sizeof (TCP_HEAD),
+ NET_BUF_TAIL
+ );
+
+ ASSERT (Nhead != NULL);
+
+ Nbuf->Tcp = Nhead;
+ Nhead->Flag = TCP_FLG_RST;
+
+ //
+ // Derive Seq/ACK from the segment if no TCB
+ // is associated with it, otherwise derive from the Tcb.
+ //
+ if (Tcb == NULL) {
+
+ if (TCP_FLG_ON (Head->Flag, TCP_FLG_ACK)) {
+ Nhead->Seq = Head->Ack;
+ Nhead->Ack = 0;
+ } else {
+ Nhead->Seq = 0;
+ TCP_SET_FLG (Nhead->Flag, TCP_FLG_ACK);
+ Nhead->Ack = HTONL (NTOHL (Head->Seq) + Len);
+ }
+ } else {
+
+ Nhead->Seq = HTONL (Tcb->SndNxt);
+ Nhead->Ack = HTONL (Tcb->RcvNxt);
+ TCP_SET_FLG (Nhead->Flag, TCP_FLG_ACK);
+ }
+
+ Nhead->SrcPort = Head->DstPort;
+ Nhead->DstPort = Head->SrcPort;
+ Nhead->HeadLen = (UINT8) (sizeof (TCP_HEAD) >> 2);
+ Nhead->Res = 0;
+ Nhead->Wnd = HTONS (0xFFFF);
+ Nhead->Checksum = 0;
+ Nhead->Urg = 0;
+
+ if (Version == IP_VERSION_4) {
+ HeadSum = NetPseudoHeadChecksum (Local->Addr[0], Remote->Addr[0], 6, 0);
+ } else {
+ HeadSum = NetIp6PseudoHeadChecksum (&Local->v6, &Remote->v6, 6, 0);
+ }
+
+ Nhead->Checksum = TcpChecksum (Nbuf, HeadSum);
+
+ TcpSendIpPacket (Tcb, Nbuf, Local, Remote, Version);
+
+ NetbufFree (Nbuf);
+
+ return 0;
+}
+
+/**
+ Verify that the segment is in good shape.
+
+ @param[in] Nbuf The buffer that contains the segment to be checked.
+
+ @retval 0 The segment is broken.
+ @retval 1 The segment is in good shape.
+
+**/
+INTN
+TcpVerifySegment (
+ IN NET_BUF *Nbuf
+ )
+{
+ TCP_HEAD *Head;
+ TCP_SEG *Seg;
+ UINT32 Len;
+
+ if (Nbuf == NULL) {
+ return 1;
+ }
+
+ NET_CHECK_SIGNATURE (Nbuf, NET_BUF_SIGNATURE);
+
+ Seg = TCPSEG_NETBUF (Nbuf);
+ Len = Nbuf->TotalSize;
+ Head = Nbuf->Tcp;
+
+ if (Head != NULL) {
+ if (Head->Flag != Seg->Flag) {
+ return 0;
+ }
+
+ Len -= (Head->HeadLen << 2);
+ }
+
+ if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
+ Len++;
+ }
+
+ if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
+ Len++;
+ }
+
+ if (Seg->Seq + Len != Seg->End) {
+ return 0;
+ }
+
+ return 1;
+}
+