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