blob: 5b74fdcdc362ff3f1f5888920bf5c2a0a90db6be [file] [log] [blame]
/** @file
TCP input process routines.
Copyright (c) 2009 - 2018, 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"
/**
Check whether the sequence number of the incoming segment is acceptable.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Seg Pointer to the incoming segment.
@retval 1 The sequence number is acceptable.
@retval 0 The sequence number is not acceptable.
**/
INTN
TcpSeqAcceptable (
IN TCP_CB *Tcb,
IN TCP_SEG *Seg
)
{
return (TCP_SEQ_LEQ (Tcb->RcvNxt, Seg->End) &&
TCP_SEQ_LT (Seg->Seq, Tcb->RcvWl2 + Tcb->RcvWnd));
}
/**
NewReno fast recovery defined in RFC3782.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Seg Segment that triggers the fast recovery.
**/
VOID
TcpFastRecover (
IN OUT TCP_CB *Tcb,
IN TCP_SEG *Seg
)
{
UINT32 FlightSize;
UINT32 Acked;
//
// Step 1: Three duplicate ACKs and not in fast recovery
//
if (Tcb->CongestState != TCP_CONGEST_RECOVER) {
//
// Step 1A: Invoking fast retransmission.
//
FlightSize = TCP_SUB_SEQ (Tcb->SndNxt, Tcb->SndUna);
Tcb->Ssthresh = MAX (FlightSize >> 1, (UINT32) (2 * Tcb->SndMss));
Tcb->Recover = Tcb->SndNxt;
Tcb->CongestState = TCP_CONGEST_RECOVER;
TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON);
//
// Step 2: Entering fast retransmission
//
TcpRetransmit (Tcb, Tcb->SndUna);
Tcb->CWnd = Tcb->Ssthresh + 3 * Tcb->SndMss;
DEBUG (
(EFI_D_NET,
"TcpFastRecover: enter fast retransmission for TCB %p, recover point is %d\n",
Tcb,
Tcb->Recover)
);
return;
}
//
// During fast recovery, execute Step 3, 4, 5 of RFC3782
//
if (Seg->Ack == Tcb->SndUna) {
//
// Step 3: Fast Recovery,
// If this is a duplicated ACK, increse Cwnd by SMSS.
//
// Step 4 is skipped here only to be executed later
// by TcpToSendData
//
Tcb->CWnd += Tcb->SndMss;
DEBUG (
(EFI_D_NET,
"TcpFastRecover: received another duplicated ACK (%d) for TCB %p\n",
Seg->Ack,
Tcb)
);
} else {
//
// New data is ACKed, check whether it is a
// full ACK or partial ACK
//
if (TCP_SEQ_GEQ (Seg->Ack, Tcb->Recover)) {
//
// Step 5 - Full ACK:
// deflate the congestion window, and exit fast recovery
//
FlightSize = TCP_SUB_SEQ (Tcb->SndNxt, Tcb->SndUna);
Tcb->CWnd = MIN (Tcb->Ssthresh, FlightSize + Tcb->SndMss);
Tcb->CongestState = TCP_CONGEST_OPEN;
DEBUG (
(EFI_D_NET,
"TcpFastRecover: received a full ACK(%d) for TCB %p, exit fast recovery\n",
Seg->Ack,
Tcb)
);
} else {
//
// Step 5 - Partial ACK:
// fast retransmit the first unacknowledge field
// , then deflate the CWnd
//
TcpRetransmit (Tcb, Seg->Ack);
Acked = TCP_SUB_SEQ (Seg->Ack, Tcb->SndUna);
//
// Deflate the CWnd by the amount of new data
// ACKed by SEG.ACK. If more than one SMSS data
// is ACKed, add back SMSS byte to CWnd after
//
if (Acked >= Tcb->SndMss) {
Acked -= Tcb->SndMss;
}
Tcb->CWnd -= Acked;
DEBUG (
(EFI_D_NET,
"TcpFastRecover: received a partial ACK(%d) for TCB %p\n",
Seg->Ack,
Tcb)
);
}
}
}
/**
NewReno fast loss recovery defined in RFC3792.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Seg Segment that triggers the fast loss recovery.
**/
VOID
TcpFastLossRecover (
IN OUT TCP_CB *Tcb,
IN TCP_SEG *Seg
)
{
if (TCP_SEQ_GT (Seg->Ack, Tcb->SndUna)) {
//
// New data is ACKed, check whether it is a
// full ACK or partial ACK
//
if (TCP_SEQ_GEQ (Seg->Ack, Tcb->LossRecover)) {
//
// Full ACK: exit the loss recovery.
//
Tcb->LossTimes = 0;
Tcb->CongestState = TCP_CONGEST_OPEN;
DEBUG (
(EFI_D_NET,
"TcpFastLossRecover: received a full ACK(%d) for TCB %p\n",
Seg->Ack,
Tcb)
);
} else {
//
// Partial ACK:
// fast retransmit the first unacknowledge field.
//
TcpRetransmit (Tcb, Seg->Ack);
DEBUG (
(EFI_D_NET,
"TcpFastLossRecover: received a partial ACK(%d) for TCB %p\n",
Seg->Ack,
Tcb)
);
}
}
}
/**
Compute the RTT as specified in RFC2988.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Measure Currently measured RTT in heartbeats.
**/
VOID
TcpComputeRtt (
IN OUT TCP_CB *Tcb,
IN UINT32 Measure
)
{
INT32 Var;
//
// Step 2.3: Compute the RTO for subsequent RTT measurement.
//
if (Tcb->SRtt != 0) {
Var = Tcb->SRtt - (Measure << TCP_RTT_SHIFT);
if (Var < 0) {
Var = -Var;
}
Tcb->RttVar = (3 * Tcb->RttVar + Var) >> 2;
Tcb->SRtt = 7 * (Tcb->SRtt >> 3) + Measure;
} else {
//
// Step 2.2: compute the first RTT measure
//
Tcb->SRtt = Measure << TCP_RTT_SHIFT;
Tcb->RttVar = Measure << (TCP_RTT_SHIFT - 1);
}
Tcb->Rto = (Tcb->SRtt + MAX (8, 4 * Tcb->RttVar)) >> TCP_RTT_SHIFT;
//
// Step 2.4: Limit the RTO to at least 1 second
// Step 2.5: Limit the RTO to a maxium value that
// is at least 60 second
//
if (Tcb->Rto < TCP_RTO_MIN) {
Tcb->Rto = TCP_RTO_MIN;
} else if (Tcb->Rto > TCP_RTO_MAX) {
Tcb->Rto = TCP_RTO_MAX;
}
DEBUG (
(EFI_D_NET,
"TcpComputeRtt: new RTT for TCB %p computed SRTT: %d RTTVAR: %d RTO: %d\n",
Tcb,
Tcb->SRtt,
Tcb->RttVar,
Tcb->Rto)
);
}
/**
Trim the data; SYN and FIN to fit into the window defined by Left and Right.
@param[in] Nbuf The buffer that contains a received TCP segment without an IP header.
@param[in] Left The sequence number of the window's left edge.
@param[in] Right The sequence number of the window's right edge.
@retval 0 The segment is broken.
@retval 1 The segment is in good shape.
**/
INTN
TcpTrimSegment (
IN NET_BUF *Nbuf,
IN TCP_SEQNO Left,
IN TCP_SEQNO Right
)
{
TCP_SEG *Seg;
TCP_SEQNO Urg;
UINT32 Drop;
Seg = TCPSEG_NETBUF (Nbuf);
//
// If the segment is completely out of window,
// truncate every thing, include SYN and FIN.
//
if (TCP_SEQ_LEQ (Seg->End, Left) || TCP_SEQ_LEQ (Right, Seg->Seq)) {
TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_SYN);
TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_FIN);
Seg->Seq = Seg->End;
NetbufTrim (Nbuf, Nbuf->TotalSize, NET_BUF_HEAD);
return 1;
}
//
// Adjust the buffer header
//
if (TCP_SEQ_LT (Seg->Seq, Left)) {
Drop = TCP_SUB_SEQ (Left, Seg->Seq);
Urg = Seg->Seq + Seg->Urg;
Seg->Seq = Left;
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_SYN);
Drop--;
}
//
// Adjust the urgent point
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_URG)) {
if (TCP_SEQ_LT (Urg, Seg->Seq)) {
TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_URG);
} else {
Seg->Urg = (UINT16) TCP_SUB_SEQ (Urg, Seg->Seq);
}
}
if (Drop != 0) {
NetbufTrim (Nbuf, Drop, NET_BUF_HEAD);
}
}
//
// Adjust the buffer tail
//
if (TCP_SEQ_GT (Seg->End, Right)) {
Drop = TCP_SUB_SEQ (Seg->End, Right);
Seg->End = Right;
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
TCP_CLEAR_FLG (Seg->Flag, TCP_FLG_FIN);
Drop--;
}
if (Drop != 0) {
NetbufTrim (Nbuf, Drop, NET_BUF_TAIL);
}
}
return TcpVerifySegment (Nbuf);
}
/**
Trim off the data outside the tcb's receive window.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Nbuf Pointer to the NET_BUF containing the received tcp segment.
@retval 0 The segment is broken.
@retval 1 The segment is in good shape.
**/
INTN
TcpTrimInWnd (
IN TCP_CB *Tcb,
IN NET_BUF *Nbuf
)
{
return TcpTrimSegment (Nbuf, Tcb->RcvNxt, Tcb->RcvWl2 + Tcb->RcvWnd);
}
/**
Process the data and FIN flag, and check whether to deliver
data to the socket layer.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@retval 0 No error occurred to deliver data.
@retval -1 An error condition occurred. The proper response is to reset the
connection.
**/
INTN
TcpDeliverData (
IN OUT TCP_CB *Tcb
)
{
LIST_ENTRY *Entry;
NET_BUF *Nbuf;
TCP_SEQNO Seq;
TCP_SEG *Seg;
UINT32 Urgent;
ASSERT ((Tcb != NULL) && (Tcb->Sk != NULL));
//
// make sure there is some data queued,
// and TCP is in a proper state
//
if (IsListEmpty (&Tcb->RcvQue) || !TCP_CONNECTED (Tcb->State)) {
return 0;
}
//
// Deliver data to the socket layer
//
Entry = Tcb->RcvQue.ForwardLink;
Seq = Tcb->RcvNxt;
while (Entry != &Tcb->RcvQue) {
Nbuf = NET_LIST_USER_STRUCT (Entry, NET_BUF, List);
Seg = TCPSEG_NETBUF (Nbuf);
if (TcpVerifySegment (Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpToSendData: discard a broken segment for TCB %p\n",
Tcb)
);
NetbufFree (Nbuf);
return -1;
}
ASSERT (Nbuf->Tcp == NULL);
if (TCP_SEQ_GT (Seg->Seq, Seq)) {
break;
}
Entry = Entry->ForwardLink;
Seq = Seg->End;
Tcb->RcvNxt = Seq;
RemoveEntryList (&Nbuf->List);
//
// RFC793 Eighth step: process FIN in sequence
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
//
// The peer sends to us junky data after FIN,
// reset the connection.
//
if (!IsListEmpty (&Tcb->RcvQue)) {
DEBUG (
(EFI_D_ERROR,
"TcpDeliverData: data received after FIN from peer of TCB %p, reset connection\n",
Tcb)
);
NetbufFree (Nbuf);
return -1;
}
DEBUG (
(EFI_D_NET,
"TcpDeliverData: processing FIN from peer of TCB %p\n",
Tcb)
);
switch (Tcb->State) {
case TCP_SYN_RCVD:
case TCP_ESTABLISHED:
TcpSetState (Tcb, TCP_CLOSE_WAIT);
break;
case TCP_FIN_WAIT_1:
if (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_ACKED)) {
TcpSetState (Tcb, TCP_CLOSING);
break;
}
//
// fall through
//
case TCP_FIN_WAIT_2:
TcpSetState (Tcb, TCP_TIME_WAIT);
TcpClearAllTimer (Tcb);
if (Tcb->TimeWaitTimeout != 0) {
TcpSetTimer (Tcb, TCP_TIMER_2MSL, Tcb->TimeWaitTimeout);
} else {
DEBUG (
(EFI_D_WARN,
"Connection closed immediately because app disables TIME_WAIT timer for %p\n",
Tcb)
);
TcpSendAck (Tcb);
TcpClose (Tcb);
}
break;
case TCP_CLOSE_WAIT:
case TCP_CLOSING:
case TCP_LAST_ACK:
case TCP_TIME_WAIT:
//
// The peer sends to us junk FIN byte. Discard
// the buffer then reset the connection
//
NetbufFree (Nbuf);
return -1;
break;
default:
break;
}
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
Seg->End--;
}
//
// Don't delay the ack if PUSH flag is on.
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_PSH)) {
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
}
if (Nbuf->TotalSize != 0) {
Urgent = 0;
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RCVD_URG) &&
TCP_SEQ_LEQ (Seg->Seq, Tcb->RcvUp))
{
if (TCP_SEQ_LEQ (Seg->End, Tcb->RcvUp)) {
Urgent = Nbuf->TotalSize;
} else {
Urgent = TCP_SUB_SEQ (Tcb->RcvUp, Seg->Seq) + 1;
}
}
SockDataRcvd (Tcb->Sk, Nbuf, Urgent);
}
if (TCP_FIN_RCVD (Tcb->State)) {
SockNoMoreData (Tcb->Sk);
}
NetbufFree (Nbuf);
}
return 0;
}
/**
Store the data into the reassemble queue.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Nbuf Pointer to the buffer containing the data to be queued.
@retval 0 An error condition occurred.
@retval 1 No error occurred to queue data.
**/
INTN
TcpQueueData (
IN OUT TCP_CB *Tcb,
IN NET_BUF *Nbuf
)
{
TCP_SEG *Seg;
LIST_ENTRY *Head;
LIST_ENTRY *Prev;
LIST_ENTRY *Cur;
NET_BUF *Node;
ASSERT ((Tcb != NULL) && (Nbuf != NULL) && (Nbuf->Tcp == NULL));
NET_GET_REF (Nbuf);
Seg = TCPSEG_NETBUF (Nbuf);
Head = &Tcb->RcvQue;
//
// Fast path to process normal case. That is,
// no out-of-order segments are received.
//
if (IsListEmpty (Head)) {
InsertTailList (Head, &Nbuf->List);
return 1;
}
//
// Find the point to insert the buffer
//
for (Prev = Head, Cur = Head->ForwardLink;
Cur != Head;
Prev = Cur, Cur = Cur->ForwardLink) {
Node = NET_LIST_USER_STRUCT (Cur, NET_BUF, List);
if (TCP_SEQ_LT (Seg->Seq, TCPSEG_NETBUF (Node)->Seq)) {
break;
}
}
//
// Check whether the current segment overlaps with the
// previous segment.
//
if (Prev != Head) {
Node = NET_LIST_USER_STRUCT (Prev, NET_BUF, List);
if (TCP_SEQ_LT (Seg->Seq, TCPSEG_NETBUF (Node)->End)) {
if (TCP_SEQ_LEQ (Seg->End, TCPSEG_NETBUF (Node)->End)) {
return 1;
}
if (TcpTrimSegment (Nbuf, TCPSEG_NETBUF (Node)->End, Seg->End) == 0) {
return 0;
}
}
}
InsertHeadList (Prev, &Nbuf->List);
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
//
// Check the segments after the insert point.
//
while (Cur != Head) {
Node = NET_LIST_USER_STRUCT (Cur, NET_BUF, List);
if (TCP_SEQ_LEQ (TCPSEG_NETBUF (Node)->End, Seg->End)) {
Cur = Cur->ForwardLink;
RemoveEntryList (&Node->List);
NetbufFree (Node);
continue;
}
if (TCP_SEQ_LT (TCPSEG_NETBUF (Node)->Seq, Seg->End)) {
if (TCP_SEQ_LEQ (TCPSEG_NETBUF (Node)->Seq, Seg->Seq)) {
RemoveEntryList (&Nbuf->List);
return 1;
}
if (TcpTrimSegment (Nbuf, Seg->Seq, TCPSEG_NETBUF (Node)->Seq) == 0) {
RemoveEntryList (&Nbuf->List);
return 0;
}
break;
}
Cur = Cur->ForwardLink;
}
return 1;
}
/**
Adjust the send queue or the retransmit queue.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Ack The acknowledge seuqence number of the received segment.
@retval 0 An error condition occurred.
@retval 1 No error occurred.
**/
INTN
TcpAdjustSndQue (
IN TCP_CB *Tcb,
IN TCP_SEQNO Ack
)
{
LIST_ENTRY *Head;
LIST_ENTRY *Cur;
NET_BUF *Node;
TCP_SEG *Seg;
Head = &Tcb->SndQue;
Cur = Head->ForwardLink;
while (Cur != Head) {
Node = NET_LIST_USER_STRUCT (Cur, NET_BUF, List);
Seg = TCPSEG_NETBUF (Node);
if (TCP_SEQ_GEQ (Seg->Seq, Ack)) {
break;
}
//
// Remove completely ACKed segments
//
if (TCP_SEQ_LEQ (Seg->End, Ack)) {
Cur = Cur->ForwardLink;
RemoveEntryList (&Node->List);
NetbufFree (Node);
continue;
}
return TcpTrimSegment (Node, Ack, Seg->End);
}
return 1;
}
/**
Process the received TCP segments.
@param[in] Nbuf Buffer that contains received a TCP segment without an IP header.
@param[in] Src Source address of the segment, or the peer's IP address.
@param[in] Dst Destination address of the segment, or the local end's IP
address.
@param[in] Version IP_VERSION_4 indicates IP4 stack. IP_VERSION_6 indicates
IP6 stack.
@retval 0 Segment processed successfully. It is either accepted or
discarded. However, no connection is reset by the segment.
@retval -1 A connection is reset by the segment.
**/
INTN
TcpInput (
IN NET_BUF *Nbuf,
IN EFI_IP_ADDRESS *Src,
IN EFI_IP_ADDRESS *Dst,
IN UINT8 Version
)
{
TCP_CB *Tcb;
TCP_CB *Parent;
TCP_OPTION Option;
TCP_HEAD *Head;
INT32 Len;
TCP_SEG *Seg;
TCP_SEQNO Right;
TCP_SEQNO Urg;
UINT16 Checksum;
INT32 Usable;
ASSERT ((Version == IP_VERSION_4) || (Version == IP_VERSION_6));
NET_CHECK_SIGNATURE (Nbuf, NET_BUF_SIGNATURE);
Parent = NULL;
Tcb = NULL;
Head = (TCP_HEAD *) NetbufGetByte (Nbuf, 0, NULL);
ASSERT (Head != NULL);
if (Nbuf->TotalSize < sizeof (TCP_HEAD)) {
DEBUG ((EFI_D_NET, "TcpInput: received a malformed packet\n"));
goto DISCARD;
}
Len = Nbuf->TotalSize - (Head->HeadLen << 2);
if ((Head->HeadLen < 5) || (Len < 0)) {
DEBUG ((EFI_D_NET, "TcpInput: received a malformed packet\n"));
goto DISCARD;
}
if (Version == IP_VERSION_4) {
Checksum = NetPseudoHeadChecksum (Src->Addr[0], Dst->Addr[0], 6, 0);
} else {
Checksum = NetIp6PseudoHeadChecksum (&Src->v6, &Dst->v6, 6, 0);
}
Checksum = TcpChecksum (Nbuf, Checksum);
if (Checksum != 0) {
DEBUG ((EFI_D_ERROR, "TcpInput: received a checksum error packet\n"));
goto DISCARD;
}
if (TCP_FLG_ON (Head->Flag, TCP_FLG_SYN)) {
Len++;
}
if (TCP_FLG_ON (Head->Flag, TCP_FLG_FIN)) {
Len++;
}
Tcb = TcpLocateTcb (
Head->DstPort,
Dst,
Head->SrcPort,
Src,
Version,
(BOOLEAN) TCP_FLG_ON (Head->Flag, TCP_FLG_SYN)
);
if ((Tcb == NULL) || (Tcb->State == TCP_CLOSED)) {
DEBUG ((EFI_D_NET, "TcpInput: send reset because no TCB found\n"));
Tcb = NULL;
goto SEND_RESET;
}
Seg = TcpFormatNetbuf (Tcb, Nbuf);
//
// RFC1122 recommended reaction to illegal option
// (in fact, an illegal option length) is reset.
//
if (TcpParseOption (Nbuf->Tcp, &Option) == -1) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: reset the peer because of malformed option for TCB %p\n",
Tcb)
);
goto SEND_RESET;
}
//
// From now on, the segment is headless
//
NetbufTrim (Nbuf, (Head->HeadLen << 2), NET_BUF_HEAD);
Nbuf->Tcp = NULL;
//
// Process the segment in LISTEN state.
//
if (Tcb->State == TCP_LISTEN) {
//
// First step: Check RST
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_RST)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: discard a reset segment for TCB %p in listening\n",
Tcb)
);
goto DISCARD;
}
//
// Second step: Check ACK.
// Any ACK sent to TCP in LISTEN is reseted.
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: send reset because of segment with ACK for TCB %p in listening\n",
Tcb)
);
goto SEND_RESET;
}
//
// Third step: Check SYN
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
//
// create a child TCB to handle the data
//
Parent = Tcb;
Tcb = TcpCloneTcb (Parent);
if (Tcb == NULL) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a segment because failed to clone a child for TCB %p\n",
Tcb)
);
goto DISCARD;
}
DEBUG (
(EFI_D_NET,
"TcpInput: create a child for TCB %p in listening\n",
Tcb)
);
//
// init the TCB structure
//
IP6_COPY_ADDRESS (&Tcb->LocalEnd.Ip, Dst);
IP6_COPY_ADDRESS (&Tcb->RemoteEnd.Ip, Src);
Tcb->LocalEnd.Port = Head->DstPort;
Tcb->RemoteEnd.Port = Head->SrcPort;
TcpInitTcbLocal (Tcb);
TcpInitTcbPeer (Tcb, Seg, &Option);
TcpSetState (Tcb, TCP_SYN_RCVD);
TcpSetTimer (Tcb, TCP_TIMER_CONNECT, Tcb->ConnectTimeout);
if (TcpTrimInWnd (Tcb, Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
goto StepSix;
}
goto DISCARD;
} else if (Tcb->State == TCP_SYN_SENT) {
//
// First step: Check ACK bit
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK) && (Seg->Ack != Tcb->Iss + 1)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: send reset because of wrong ACK received for TCB %p in SYN_SENT\n",
Tcb)
);
goto SEND_RESET;
}
//
// Second step: Check RST bit
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_RST)) {
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: connection reset by peer for TCB %p in SYN_SENT\n",
Tcb)
);
SOCK_ERROR (Tcb->Sk, EFI_CONNECTION_RESET);
goto DROP_CONNECTION;
} else {
DEBUG (
(EFI_D_WARN,
"TcpInput: discard a reset segment because of no ACK for TCB %p in SYN_SENT\n",
Tcb)
);
goto DISCARD;
}
}
//
// Third step: Check security and precedence. Skipped
//
//
// Fourth step: Check SYN. Pay attention to simultaneous open
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
TcpInitTcbPeer (Tcb, Seg, &Option);
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
Tcb->SndUna = Seg->Ack;
}
TcpClearTimer (Tcb, TCP_TIMER_REXMIT);
if (TCP_SEQ_GT (Tcb->SndUna, Tcb->Iss)) {
TcpSetState (Tcb, TCP_ESTABLISHED);
TcpClearTimer (Tcb, TCP_TIMER_CONNECT);
TcpDeliverData (Tcb);
if ((Tcb->CongestState == TCP_CONGEST_OPEN) &&
TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RTT_ON))
{
TcpComputeRtt (Tcb, Tcb->RttMeasure);
TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON);
}
if (TcpTrimInWnd (Tcb, Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
DEBUG (
(EFI_D_NET,
"TcpInput: connection established for TCB %p in SYN_SENT\n",
Tcb)
);
goto StepSix;
} else {
//
// Received a SYN segment without ACK, simultanous open.
//
TcpSetState (Tcb, TCP_SYN_RCVD);
ASSERT (Tcb->SndNxt == Tcb->Iss + 1);
if (TcpAdjustSndQue (Tcb, Tcb->SndNxt) == 0 || TcpTrimInWnd (Tcb, Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
DEBUG (
(EFI_D_WARN,
"TcpInput: simultaneous open for TCB %p in SYN_SENT\n",
Tcb)
);
goto StepSix;
}
}
goto DISCARD;
}
//
// Process segment in SYN_RCVD or TCP_CONNECTED states
//
//
// Clear probe timer since the RecvWindow is opened.
//
if (Tcb->ProbeTimerOn && (Seg->Wnd != 0)) {
TcpClearTimer (Tcb, TCP_TIMER_PROBE);
Tcb->ProbeTimerOn = FALSE;
}
//
// First step: Check whether SEG.SEQ is acceptable
//
if (TcpSeqAcceptable (Tcb, Seg) == 0) {
DEBUG (
(EFI_D_WARN,
"TcpInput: sequence acceptance test failed for segment of TCB %p\n",
Tcb)
);
if (!TCP_FLG_ON (Seg->Flag, TCP_FLG_RST)) {
TcpSendAck (Tcb);
}
goto DISCARD;
}
if ((TCP_SEQ_LT (Seg->Seq, Tcb->RcvWl2)) &&
(Tcb->RcvWl2 == Seg->End) &&
!TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN | TCP_FLG_FIN))
{
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
}
//
// Second step: Check the RST
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_RST)) {
DEBUG ((EFI_D_WARN, "TcpInput: connection reset for TCB %p\n", Tcb));
if (Tcb->State == TCP_SYN_RCVD) {
SOCK_ERROR (Tcb->Sk, EFI_CONNECTION_REFUSED);
//
// This TCB comes from either a LISTEN TCB,
// or active open TCB with simultanous open.
// Do NOT signal user CONNECTION refused
// if it comes from a LISTEN TCB.
//
} else if ((Tcb->State == TCP_ESTABLISHED) ||
(Tcb->State == TCP_FIN_WAIT_1) ||
(Tcb->State == TCP_FIN_WAIT_2) ||
(Tcb->State == TCP_CLOSE_WAIT))
{
SOCK_ERROR (Tcb->Sk, EFI_CONNECTION_RESET);
} else {
}
goto DROP_CONNECTION;
}
//
// Trim the data and flags.
//
if (TcpTrimInWnd (Tcb, Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
//
// Third step: Check security and precedence, Ignored
//
//
// Fourth step: Check the SYN bit.
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: connection reset because received extra SYN for TCB %p\n",
Tcb)
);
SOCK_ERROR (Tcb->Sk, EFI_CONNECTION_RESET);
goto RESET_THEN_DROP;
}
//
// Fifth step: Check the ACK
//
if (!TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: segment discard because of no ACK for connected TCB %p\n",
Tcb)
);
goto DISCARD;
} else {
if (Tcb->IpInfo->IpVersion == IP_VERSION_6 && Tcb->Tick == 0) {
Tcp6RefreshNeighbor (Tcb, Src, TCP6_KEEP_NEIGHBOR_TIME * TICKS_PER_SECOND);
Tcb->Tick = TCP6_REFRESH_NEIGHBOR_TICK;
}
}
if (Tcb->State == TCP_SYN_RCVD) {
if (TCP_SEQ_LT (Tcb->SndUna, Seg->Ack) &&
TCP_SEQ_LEQ (Seg->Ack, Tcb->SndNxt))
{
Tcb->SndWnd = Seg->Wnd;
Tcb->SndWndMax = MAX (Tcb->SndWnd, Tcb->SndWndMax);
Tcb->SndWl1 = Seg->Seq;
Tcb->SndWl2 = Seg->Ack;
TcpSetState (Tcb, TCP_ESTABLISHED);
TcpClearTimer (Tcb, TCP_TIMER_CONNECT);
TcpDeliverData (Tcb);
DEBUG (
(EFI_D_NET,
"TcpInput: connection established for TCB %p in SYN_RCVD\n",
Tcb)
);
//
// Continue the process as ESTABLISHED state
//
} else {
DEBUG (
(EFI_D_WARN,
"TcpInput: send reset because of wrong ACK for TCB %p in SYN_RCVD\n",
Tcb)
);
goto SEND_RESET;
}
}
if (TCP_SEQ_LT (Seg->Ack, Tcb->SndUna)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: ignore the out-of-data ACK for connected TCB %p\n",
Tcb)
);
goto StepSix;
} else if (TCP_SEQ_GT (Seg->Ack, Tcb->SndNxt)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: discard segment for future ACK for connected TCB %p\n",
Tcb)
);
TcpSendAck (Tcb);
goto DISCARD;
}
//
// From now on: SND.UNA <= SEG.ACK <= SND.NXT.
//
if (TCP_FLG_ON (Option.Flag, TCP_OPTION_RCVD_TS)) {
//
// update TsRecent as specified in page 16 RFC1323.
// RcvWl2 equals to the variable "LastAckSent"
// defined there.
//
if (TCP_SEQ_LEQ (Seg->Seq, Tcb->RcvWl2) &&
TCP_SEQ_LT (Tcb->RcvWl2, Seg->End))
{
Tcb->TsRecent = Option.TSVal;
Tcb->TsRecentAge = mTcpTick;
}
TcpComputeRtt (Tcb, TCP_SUB_TIME (mTcpTick, Option.TSEcr));
} else if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RTT_ON)) {
ASSERT (Tcb->CongestState == TCP_CONGEST_OPEN);
TcpComputeRtt (Tcb, Tcb->RttMeasure);
TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON);
}
if (Seg->Ack == Tcb->SndNxt) {
TcpClearTimer (Tcb, TCP_TIMER_REXMIT);
} else {
TcpSetTimer (Tcb, TCP_TIMER_REXMIT, Tcb->Rto);
}
//
// Count duplicate acks.
//
if ((Seg->Ack == Tcb->SndUna) &&
(Tcb->SndUna != Tcb->SndNxt) &&
(Seg->Wnd == Tcb->SndWnd) &&
(0 == Len))
{
Tcb->DupAck++;
} else {
Tcb->DupAck = 0;
}
//
// Congestion avoidance, fast recovery and fast retransmission.
//
if (((Tcb->CongestState == TCP_CONGEST_OPEN) && (Tcb->DupAck < 3)) ||
(Tcb->CongestState == TCP_CONGEST_LOSS))
{
if (TCP_SEQ_GT (Seg->Ack, Tcb->SndUna)) {
if (Tcb->CWnd < Tcb->Ssthresh) {
Tcb->CWnd += Tcb->SndMss;
} else {
Tcb->CWnd += MAX (Tcb->SndMss * Tcb->SndMss / Tcb->CWnd, 1);
}
Tcb->CWnd = MIN (Tcb->CWnd, TCP_MAX_WIN << Tcb->SndWndScale);
}
if (Tcb->CongestState == TCP_CONGEST_LOSS) {
TcpFastLossRecover (Tcb, Seg);
}
} else {
TcpFastRecover (Tcb, Seg);
}
if (TCP_SEQ_GT (Seg->Ack, Tcb->SndUna)) {
if (TcpAdjustSndQue (Tcb, Seg->Ack) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
Tcb->SndUna = Seg->Ack;
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_SND_URG) &&
TCP_SEQ_LT (Tcb->SndUp, Seg->Ack))
{
TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_URG);
}
}
//
// Update window info
//
if (TCP_SEQ_LT (Tcb->SndWl1, Seg->Seq) ||
((Tcb->SndWl1 == Seg->Seq) && TCP_SEQ_LEQ (Tcb->SndWl2, Seg->Ack)))
{
Right = Seg->Ack + Seg->Wnd;
if (TCP_SEQ_LT (Right, Tcb->SndWl2 + Tcb->SndWnd)) {
if ((Tcb->SndWl1 == Seg->Seq) &&
(Tcb->SndWl2 == Seg->Ack) &&
(Len == 0))
{
goto NO_UPDATE;
}
DEBUG (
(EFI_D_WARN,
"TcpInput: peer shrinks the window for connected TCB %p\n",
Tcb)
);
if ((Tcb->CongestState == TCP_CONGEST_RECOVER) &&
(TCP_SEQ_LT (Right, Tcb->Recover)))
{
Tcb->Recover = Right;
}
if ((Tcb->CongestState == TCP_CONGEST_LOSS) &&
(TCP_SEQ_LT (Right, Tcb->LossRecover)))
{
Tcb->LossRecover = Right;
}
if (TCP_SEQ_LT (Right, Tcb->SndNxt)) {
//
// Check for Window Retraction in RFC7923 section 2.4.
// The lower n bits of the peer's actual receive window is wiped out if TCP
// window scale is enabled, it will look like the peer is shrinking the window.
// Check whether the SndNxt is out of the advertised receive window by more than
// 2^Rcv.Wind.Shift before moving the SndNxt to the left.
//
DEBUG (
(EFI_D_WARN,
"TcpInput: peer advise negative useable window for connected TCB %p\n",
Tcb)
);
Usable = TCP_SUB_SEQ (Tcb->SndNxt, Right);
if ((Usable >> Tcb->SndWndScale) > 0) {
DEBUG (
(EFI_D_WARN,
"TcpInput: SndNxt is out of window by more than window scale for TCB %p\n",
Tcb)
);
Tcb->SndNxt = Right;
}
if (Right == Tcb->SndUna) {
TcpClearTimer (Tcb, TCP_TIMER_REXMIT);
TcpSetProbeTimer (Tcb);
}
}
}
Tcb->SndWnd = Seg->Wnd;
Tcb->SndWndMax = MAX (Tcb->SndWnd, Tcb->SndWndMax);
Tcb->SndWl1 = Seg->Seq;
Tcb->SndWl2 = Seg->Ack;
}
NO_UPDATE:
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_SENT) &&
(Tcb->SndUna == Tcb->SndNxt))
{
DEBUG (
(EFI_D_NET,
"TcpInput: local FIN is ACKed by peer for connected TCB %p\n",
Tcb)
);
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_FIN_ACKED);
}
//
// Transit the state if proper.
//
switch (Tcb->State) {
case TCP_FIN_WAIT_1:
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_ACKED)) {
TcpSetState (Tcb, TCP_FIN_WAIT_2);
TcpClearAllTimer (Tcb);
TcpSetTimer (Tcb, TCP_TIMER_FINWAIT2, Tcb->FinWait2Timeout);
}
case TCP_FIN_WAIT_2:
break;
case TCP_CLOSE_WAIT:
break;
case TCP_CLOSING:
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_ACKED)) {
TcpSetState (Tcb, TCP_TIME_WAIT);
TcpClearAllTimer (Tcb);
if (Tcb->TimeWaitTimeout != 0) {
TcpSetTimer (Tcb, TCP_TIMER_2MSL, Tcb->TimeWaitTimeout);
} else {
DEBUG (
(EFI_D_WARN,
"Connection closed immediately because app disables TIME_WAIT timer for %p\n",
Tcb)
);
TcpClose (Tcb);
}
}
break;
case TCP_LAST_ACK:
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_FIN_ACKED)) {
TcpSetState (Tcb, TCP_CLOSED);
}
break;
case TCP_TIME_WAIT:
TcpSendAck (Tcb);
if (Tcb->TimeWaitTimeout != 0) {
TcpSetTimer (Tcb, TCP_TIMER_2MSL, Tcb->TimeWaitTimeout);
} else {
DEBUG (
(EFI_D_WARN,
"Connection closed immediately because app disables TIME_WAIT timer for %p\n",
Tcb)
);
TcpClose (Tcb);
}
break;
default:
break;
}
//
// Sixth step: Check the URG bit.update the Urg point
// if in TCP_CAN_RECV, otherwise, leave the RcvUp intact.
//
StepSix:
Tcb->Idle = 0;
TcpSetKeepaliveTimer (Tcb);
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_URG) && !TCP_FIN_RCVD (Tcb->State)) {
DEBUG (
(EFI_D_NET,
"TcpInput: received urgent data from peer for connected TCB %p\n",
Tcb)
);
Urg = Seg->Seq + Seg->Urg;
if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RCVD_URG) &&
TCP_SEQ_GT (Urg, Tcb->RcvUp))
{
Tcb->RcvUp = Urg;
} else {
Tcb->RcvUp = Urg;
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RCVD_URG);
}
}
//
// Seventh step: Process the segment data
//
if (Seg->End != Seg->Seq) {
if (TCP_FIN_RCVD (Tcb->State)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: connection reset because data is lost for connected TCB %p\n",
Tcb)
);
goto RESET_THEN_DROP;
}
if (TCP_LOCAL_CLOSED (Tcb->State) && (Nbuf->TotalSize != 0)) {
DEBUG (
(EFI_D_WARN,
"TcpInput: connection reset because data is lost for connected TCB %p\n",
Tcb)
);
goto RESET_THEN_DROP;
}
if (TcpQueueData (Tcb, Nbuf) == 0) {
DEBUG (
(EFI_D_ERROR,
"TcpInput: discard a broken segment for TCB %p\n",
Tcb)
);
goto DISCARD;
}
if (TcpDeliverData (Tcb) == -1) {
goto RESET_THEN_DROP;
}
if (!IsListEmpty (&Tcb->RcvQue)) {
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW);
}
}
//
// Eighth step: check the FIN.
// This step is moved to TcpDeliverData. FIN will be
// processed in sequence there. Check the comments in
// the beginning of the file header for information.
//
//
// Tcb is a new child of the listening Parent,
// commit it.
//
if (Parent != NULL) {
Tcb->Parent = Parent;
TcpInsertTcb (Tcb);
}
if ((Tcb->State != TCP_CLOSED) &&
(TcpToSendData (Tcb, 0) == 0) &&
(TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_ACK_NOW) || (Nbuf->TotalSize != 0)))
{
TcpToSendAck (Tcb);
}
NetbufFree (Nbuf);
return 0;
RESET_THEN_DROP:
TcpSendReset (Tcb, Head, Len, Dst, Src, Version);
DROP_CONNECTION:
ASSERT ((Tcb != NULL) && (Tcb->Sk != NULL));
NetbufFree (Nbuf);
TcpClose (Tcb);
return -1;
SEND_RESET:
TcpSendReset (Tcb, Head, Len, Dst, Src, Version);
DISCARD:
//
// Tcb is a child of Parent, and it doesn't survive
//
DEBUG ((EFI_D_WARN, "TcpInput: Discard a packet\n"));
NetbufFree (Nbuf);
if ((Parent != NULL) && (Tcb != NULL)) {
ASSERT (Tcb->Sk != NULL);
TcpClose (Tcb);
}
return 0;
}
/**
Process the received ICMP error messages for TCP.
@param[in] Nbuf The buffer that contains part of the TCP segment without an IP header
truncated from the ICMP error packet.
@param[in] IcmpErr The ICMP error code interpreted from an ICMP error packet.
@param[in] Src Source address of the ICMP error message.
@param[in] Dst Destination address of the ICMP error message.
@param[in] Version IP_VERSION_4 indicates IP4 stack. IP_VERSION_6 indicates
IP6 stack.
**/
VOID
TcpIcmpInput (
IN NET_BUF *Nbuf,
IN UINT8 IcmpErr,
IN EFI_IP_ADDRESS *Src,
IN EFI_IP_ADDRESS *Dst,
IN UINT8 Version
)
{
TCP_HEAD *Head;
TCP_CB *Tcb;
TCP_SEQNO Seq;
EFI_STATUS IcmpErrStatus;
BOOLEAN IcmpErrIsHard;
BOOLEAN IcmpErrNotify;
if (Nbuf->TotalSize < sizeof (TCP_HEAD)) {
goto CLEAN_EXIT;
}
Head = (TCP_HEAD *) NetbufGetByte (Nbuf, 0, NULL);
ASSERT (Head != NULL);
Tcb = TcpLocateTcb (
Head->DstPort,
Dst,
Head->SrcPort,
Src,
Version,
FALSE
);
if (Tcb == NULL || Tcb->State == TCP_CLOSED) {
goto CLEAN_EXIT;
}
//
// Validate the sequence number.
//
Seq = NTOHL (Head->Seq);
if (!(TCP_SEQ_LEQ (Tcb->SndUna, Seq) && TCP_SEQ_LT (Seq, Tcb->SndNxt))) {
goto CLEAN_EXIT;
}
IcmpErrStatus = IpIoGetIcmpErrStatus (
IcmpErr,
Tcb->Sk->IpVersion,
&IcmpErrIsHard,
&IcmpErrNotify
);
if (IcmpErrNotify) {
SOCK_ERROR (Tcb->Sk, IcmpErrStatus);
}
if (IcmpErrIsHard) {
TcpClose (Tcb);
}
CLEAN_EXIT:
NetbufFree (Nbuf);
}