blob: 3310306f639ccb724e01434f1447cf78380f1255 [file] [log] [blame]
/** @file
Misc support routines for TCP driver.
(C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
Copyright (c) 2009 - 2017, Intel Corporation. All rights reserved.<BR>
Copyright (c) Microsoft Corporation
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "TcpMain.h"
LIST_ENTRY mTcpRunQue = {
&mTcpRunQue,
&mTcpRunQue
};
LIST_ENTRY mTcpListenQue = {
&mTcpListenQue,
&mTcpListenQue
};
//
// The Session secret
// This must be initialized to a random value at boot time
//
TCP_SEQNO mTcpGlobalSecret;
//
// Union to hold either an IPv4 or IPv6 address
// This is used to simplify the ISN hash computation
//
typedef union {
UINT8 IPv4[4];
UINT8 IPv6[16];
} NETWORK_ADDRESS;
//
// The ISN is computed by hashing this structure
// It is initialized with the local and remote IP addresses and ports
// and the secret
//
//
typedef struct {
UINT16 LocalPort;
UINT16 RemotePort;
NETWORK_ADDRESS LocalAddress;
NETWORK_ADDRESS RemoteAddress;
TCP_SEQNO Secret;
} ISN_HASH_CTX;
CHAR16 *mTcpStateName[] = {
L"TCP_CLOSED",
L"TCP_LISTEN",
L"TCP_SYN_SENT",
L"TCP_SYN_RCVD",
L"TCP_ESTABLISHED",
L"TCP_FIN_WAIT_1",
L"TCP_FIN_WAIT_2",
L"TCP_CLOSING",
L"TCP_TIME_WAIT",
L"TCP_CLOSE_WAIT",
L"TCP_LAST_ACK"
};
/**
Initialize the Tcb local related members.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@retval EFI_SUCCESS The operation completed successfully
@retval others The underlying functions failed and could not complete the operation
**/
EFI_STATUS
TcpInitTcbLocal (
IN OUT TCP_CB *Tcb
)
{
TCP_SEQNO Isn;
EFI_STATUS Status;
//
// Compute the checksum of the fixed parts of pseudo header
//
if (Tcb->Sk->IpVersion == IP_VERSION_4) {
Tcb->HeadSum = NetPseudoHeadChecksum (
Tcb->LocalEnd.Ip.Addr[0],
Tcb->RemoteEnd.Ip.Addr[0],
0x06,
0
);
Status = TcpGetIsn (
Tcb->LocalEnd.Ip.v4.Addr,
sizeof (IPv4_ADDRESS),
Tcb->LocalEnd.Port,
Tcb->RemoteEnd.Ip.v4.Addr,
sizeof (IPv4_ADDRESS),
Tcb->RemoteEnd.Port,
&Isn
);
} else {
Tcb->HeadSum = NetIp6PseudoHeadChecksum (
&Tcb->LocalEnd.Ip.v6,
&Tcb->RemoteEnd.Ip.v6,
0x06,
0
);
Status = TcpGetIsn (
Tcb->LocalEnd.Ip.v6.Addr,
sizeof (IPv6_ADDRESS),
Tcb->LocalEnd.Port,
Tcb->RemoteEnd.Ip.v6.Addr,
sizeof (IPv6_ADDRESS),
Tcb->RemoteEnd.Port,
&Isn
);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "TcpInitTcbLocal: failed to get isn\n"));
ASSERT (FALSE);
return Status;
}
Tcb->Iss = Isn;
Tcb->SndUna = Tcb->Iss;
Tcb->SndNxt = Tcb->Iss;
Tcb->SndWl2 = Tcb->Iss;
Tcb->SndWnd = 536;
Tcb->RcvWnd = GET_RCV_BUFFSIZE (Tcb->Sk);
//
// First window size is never scaled
//
Tcb->RcvWndScale = 0;
Tcb->RetxmitSeqMax = 0;
Tcb->ProbeTimerOn = FALSE;
return EFI_SUCCESS;
}
/**
Initialize the peer related members.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] Seg Pointer to the segment that contains the peer's initial info.
@param[in] Opt Pointer to the options announced by the peer.
**/
VOID
TcpInitTcbPeer (
IN OUT TCP_CB *Tcb,
IN TCP_SEG *Seg,
IN TCP_OPTION *Opt
)
{
UINT16 RcvMss;
ASSERT ((Tcb != NULL) && (Seg != NULL) && (Opt != NULL));
ASSERT (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN));
Tcb->SndWnd = Seg->Wnd;
Tcb->SndWndMax = Tcb->SndWnd;
Tcb->SndWl1 = Seg->Seq;
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
Tcb->SndWl2 = Seg->Ack;
} else {
Tcb->SndWl2 = Tcb->Iss + 1;
}
if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_MSS)) {
Tcb->SndMss = (UINT16)MAX (64, Opt->Mss);
RcvMss = TcpGetRcvMss (Tcb->Sk);
if (Tcb->SndMss > RcvMss) {
Tcb->SndMss = RcvMss;
}
} else {
//
// One end doesn't support MSS option, use default.
//
Tcb->RcvMss = 536;
}
Tcb->CWnd = Tcb->SndMss;
Tcb->Irs = Seg->Seq;
Tcb->RcvNxt = Tcb->Irs + 1;
Tcb->RcvWl2 = Tcb->RcvNxt;
if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_WS) && !TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS)) {
Tcb->SndWndScale = Opt->WndScale;
Tcb->RcvWndScale = TcpComputeScale (Tcb);
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RCVD_WS);
} else {
//
// One end doesn't support window scale option. use zero.
//
Tcb->RcvWndScale = 0;
}
if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_TS) && !TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS)) {
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_TS);
TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RCVD_TS);
Tcb->TsRecent = Opt->TSVal;
//
// Compute the effective SndMss per RFC1122
// section 4.2.2.6. If timestamp option is
// enabled, it will always occupy 12 bytes.
//
Tcb->SndMss -= TCP_OPTION_TS_ALIGNED_LEN;
}
}
/**
Check whether one IP address equals the other.
@param[in] Ip1 Pointer to IP address to be checked.
@param[in] Ip2 Pointer to IP address to be checked.
@param[in] Version IP_VERSION_4 indicates the IP address is an IPv4 address,
IP_VERSION_6 indicates the IP address is an IPv6 address.
@retval TRUE Ip1 equals Ip2.
@retval FALSE Ip1 does not equal Ip2.
**/
BOOLEAN
TcpIsIpEqual (
IN EFI_IP_ADDRESS *Ip1,
IN EFI_IP_ADDRESS *Ip2,
IN UINT8 Version
)
{
ASSERT ((Version == IP_VERSION_4) || (Version == IP_VERSION_6));
if (Version == IP_VERSION_4) {
return (BOOLEAN)(Ip1->Addr[0] == Ip2->Addr[0]);
} else {
return (BOOLEAN)EFI_IP6_EQUAL (&Ip1->v6, &Ip2->v6);
}
}
/**
Check whether one IP address is filled with ZERO.
@param[in] Ip Pointer to the IP address to be checked.
@param[in] Version IP_VERSION_4 indicates the IP address is an IPv4 address,
IP_VERSION_6 indicates the IP address is an IPv6 address.
@retval TRUE Ip is all zero address.
@retval FALSE Ip is not all zero address.
**/
BOOLEAN
TcpIsIpZero (
IN EFI_IP_ADDRESS *Ip,
IN UINT8 Version
)
{
ASSERT ((Version == IP_VERSION_4) || (Version == IP_VERSION_6));
if (Version == IP_VERSION_4) {
return (BOOLEAN)(Ip->Addr[0] == 0);
} else {
return (BOOLEAN)((Ip->Addr[0] == 0) && (Ip->Addr[1] == 0) &&
(Ip->Addr[2] == 0) && (Ip->Addr[3] == 0));
}
}
/**
Locate a listen TCB that matchs the Local and Remote.
@param[in] Local Pointer to the local (IP, Port).
@param[in] Remote Pointer to the remote (IP, Port).
@param[in] Version IP_VERSION_4 indicates TCP is running on IP4 stack,
IP_VERSION_6 indicates TCP is running on IP6 stack.
@return Pointer to the TCP_CB with the least number of wildcards,
if NULL no match is found.
**/
TCP_CB *
TcpLocateListenTcb (
IN TCP_PEER *Local,
IN TCP_PEER *Remote,
IN UINT8 Version
)
{
LIST_ENTRY *Entry;
TCP_CB *Node;
TCP_CB *Match;
INTN Last;
INTN Cur;
Last = 4;
Match = NULL;
NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
Node = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
if ((Version != Node->Sk->IpVersion) ||
(Local->Port != Node->LocalEnd.Port) ||
!TCP_PEER_MATCH (Remote, &Node->RemoteEnd, Version) ||
!TCP_PEER_MATCH (Local, &Node->LocalEnd, Version)
)
{
continue;
}
//
// Compute the number of wildcard
//
Cur = 0;
if (TcpIsIpZero (&Node->RemoteEnd.Ip, Version)) {
Cur++;
}
if (Node->RemoteEnd.Port == 0) {
Cur++;
}
if (TcpIsIpZero (&Node->LocalEnd.Ip, Version)) {
Cur++;
}
if (Cur < Last) {
if (Cur == 0) {
return Node;
}
Last = Cur;
Match = Node;
}
}
return Match;
}
/**
Try to find one Tcb whose <Ip, Port> equals to <IN Addr, IN Port>.
@param[in] Addr Pointer to the IP address needs to match.
@param[in] Port The port number needs to match.
@param[in] Version IP_VERSION_4 indicates TCP is running on IP4 stack,
IP_VERSION_6 indicates TCP is running on IP6 stack.
@retval TRUE The Tcb which matches the <Addr Port> pair exists.
@retval FALSE Otherwise
**/
BOOLEAN
TcpFindTcbByPeer (
IN EFI_IP_ADDRESS *Addr,
IN TCP_PORTNO Port,
IN UINT8 Version
)
{
TCP_PORTNO LocalPort;
LIST_ENTRY *Entry;
TCP_CB *Tcb;
ASSERT ((Addr != NULL) && (Port != 0));
LocalPort = HTONS (Port);
NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
if ((Version == Tcb->Sk->IpVersion) &&
TcpIsIpEqual (Addr, &Tcb->LocalEnd.Ip, Version) &&
(LocalPort == Tcb->LocalEnd.Port)
)
{
return TRUE;
}
}
NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
if ((Version == Tcb->Sk->IpVersion) &&
TcpIsIpEqual (Addr, &Tcb->LocalEnd.Ip, Version) &&
(LocalPort == Tcb->LocalEnd.Port)
)
{
return TRUE;
}
}
return FALSE;
}
/**
Locate the TCP_CB related to the socket pair.
@param[in] LocalPort The local port number.
@param[in] LocalIp The local IP address.
@param[in] RemotePort The remote port number.
@param[in] RemoteIp The remote 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.
@param[in] Syn If TRUE, the listen sockets are searched.
@return Pointer to the related TCP_CB. If NULL, no match is found.
**/
TCP_CB *
TcpLocateTcb (
IN TCP_PORTNO LocalPort,
IN EFI_IP_ADDRESS *LocalIp,
IN TCP_PORTNO RemotePort,
IN EFI_IP_ADDRESS *RemoteIp,
IN UINT8 Version,
IN BOOLEAN Syn
)
{
TCP_PEER Local;
TCP_PEER Remote;
LIST_ENTRY *Entry;
TCP_CB *Tcb;
Local.Port = LocalPort;
Remote.Port = RemotePort;
CopyMem (&Local.Ip, LocalIp, sizeof (EFI_IP_ADDRESS));
CopyMem (&Remote.Ip, RemoteIp, sizeof (EFI_IP_ADDRESS));
//
// First check for exact match.
//
NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
if ((Version == Tcb->Sk->IpVersion) &&
TCP_PEER_EQUAL (&Remote, &Tcb->RemoteEnd, Version) &&
TCP_PEER_EQUAL (&Local, &Tcb->LocalEnd, Version)
)
{
RemoveEntryList (&Tcb->List);
InsertHeadList (&mTcpRunQue, &Tcb->List);
return Tcb;
}
}
//
// Only check the listen queue when the SYN flag is on.
//
if (Syn) {
return TcpLocateListenTcb (&Local, &Remote, Version);
}
return NULL;
}
/**
Insert a Tcb into the proper queue.
@param[in] Tcb Pointer to the TCP_CB to be inserted.
@retval 0 The Tcb was inserted successfully.
@retval -1 Error condition occurred.
**/
INTN
TcpInsertTcb (
IN TCP_CB *Tcb
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Head;
TCP_CB *Node;
ASSERT (
(Tcb != NULL) &&
(
(Tcb->State == TCP_LISTEN) ||
(Tcb->State == TCP_SYN_SENT) ||
(Tcb->State == TCP_SYN_RCVD) ||
(Tcb->State == TCP_CLOSED)
)
);
if (Tcb->LocalEnd.Port == 0) {
return -1;
}
Head = &mTcpRunQue;
if (Tcb->State == TCP_LISTEN) {
Head = &mTcpListenQue;
}
//
// Check that the Tcb isn't already on the list.
//
NET_LIST_FOR_EACH (Entry, Head) {
Node = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
if (TCP_PEER_EQUAL (&Tcb->LocalEnd, &Node->LocalEnd, Tcb->Sk->IpVersion) &&
TCP_PEER_EQUAL (&Tcb->RemoteEnd, &Node->RemoteEnd, Tcb->Sk->IpVersion)
)
{
return -1;
}
}
InsertHeadList (Head, &Tcb->List);
return 0;
}
/**
Clone a TCP_CB from Tcb.
@param[in] Tcb Pointer to the TCP_CB to be cloned.
@return Pointer to the new cloned TCP_CB; if NULL, error condition occurred.
**/
TCP_CB *
TcpCloneTcb (
IN TCP_CB *Tcb
)
{
TCP_CB *Clone;
Clone = AllocateZeroPool (sizeof (TCP_CB));
if (Clone == NULL) {
return NULL;
}
CopyMem (Clone, Tcb, sizeof (TCP_CB));
//
// Increase the reference count of the shared IpInfo.
//
NET_GET_REF (Tcb->IpInfo);
InitializeListHead (&Clone->List);
InitializeListHead (&Clone->SndQue);
InitializeListHead (&Clone->RcvQue);
Clone->Sk = SockClone (Tcb->Sk);
if (Clone->Sk == NULL) {
DEBUG ((DEBUG_ERROR, "TcpCloneTcb: failed to clone a sock\n"));
FreePool (Clone);
return NULL;
}
((TCP_PROTO_DATA *)(Clone->Sk->ProtoReserved))->TcpPcb = Clone;
return Clone;
}
/**
Retrieves the Initial Sequence Number (ISN) for a TCP connection identified by local
and remote IP addresses and ports.
This method is based on https://datatracker.ietf.org/doc/html/rfc9293#section-3.4.1
Where the ISN is computed as follows:
ISN = TimeStamp + MD5(LocalIP, LocalPort, RemoteIP, RemotePort, Secret)
Otherwise:
ISN = M + F(localip, localport, remoteip, remoteport, secretkey)
"Here M is the 4 microsecond timer, and F() is a pseudorandom function (PRF) of the
connection's identifying parameters ("localip, localport, remoteip, remoteport")
and a secret key ("secretkey") (SHLD-1). F() MUST NOT be computable from the
outside (MUST-9), or an attacker could still guess at sequence numbers from the
ISN used for some other connection. The PRF could be implemented as a
cryptographic hash of the concatenation of the TCP connection parameters and some
secret data. For discussion of the selection of a specific hash algorithm and
management of the secret key data."
@param[in] LocalIp A pointer to the local IP address of the TCP connection.
@param[in] LocalIpSize The size, in bytes, of the LocalIp buffer.
@param[in] LocalPort The local port number of the TCP connection.
@param[in] RemoteIp A pointer to the remote IP address of the TCP connection.
@param[in] RemoteIpSize The size, in bytes, of the RemoteIp buffer.
@param[in] RemotePort The remote port number of the TCP connection.
@param[out] Isn A pointer to the variable that will receive the Initial
Sequence Number (ISN).
@retval EFI_SUCCESS The operation completed successfully, and the ISN was
retrieved.
@retval EFI_INVALID_PARAMETER One or more of the input parameters are invalid.
@retval EFI_UNSUPPORTED The operation is not supported.
**/
EFI_STATUS
TcpGetIsn (
IN UINT8 *LocalIp,
IN UINTN LocalIpSize,
IN UINT16 LocalPort,
IN UINT8 *RemoteIp,
IN UINTN RemoteIpSize,
IN UINT16 RemotePort,
OUT TCP_SEQNO *Isn
)
{
EFI_STATUS Status;
EFI_HASH2_PROTOCOL *Hash2Protocol;
EFI_HASH2_OUTPUT HashResult;
ISN_HASH_CTX IsnHashCtx;
EFI_TIME TimeStamp;
//
// Check that the ISN pointer is valid
//
if (Isn == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// The local ip may be a v4 or v6 address and may not be NULL
//
if ((LocalIp == NULL) || (LocalIpSize == 0) || (RemoteIp == NULL) || (RemoteIpSize == 0)) {
return EFI_INVALID_PARAMETER;
}
//
// the local ip may be a v4 or v6 address
//
if ((LocalIpSize != sizeof (EFI_IPv4_ADDRESS)) && (LocalIpSize != sizeof (EFI_IPv6_ADDRESS))) {
return EFI_INVALID_PARAMETER;
}
//
// Locate the Hash Protocol
//
Status = gBS->LocateProtocol (&gEfiHash2ProtocolGuid, NULL, (VOID **)&Hash2Protocol);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_NET, "Failed to locate Hash Protocol: %r\n", Status));
//
// TcpCreateService(..) is expected to be called prior to this function
//
ASSERT_EFI_ERROR (Status);
return Status;
}
//
// Initialize the hash algorithm
//
Status = Hash2Protocol->HashInit (Hash2Protocol, &gEfiHashAlgorithmSha256Guid);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_NET, "Failed to initialize sha256 hash algorithm: %r\n", Status));
return Status;
}
IsnHashCtx.LocalPort = LocalPort;
IsnHashCtx.RemotePort = RemotePort;
IsnHashCtx.Secret = mTcpGlobalSecret;
//
// Check the IP address family and copy accordingly
//
if (LocalIpSize == sizeof (EFI_IPv4_ADDRESS)) {
CopyMem (&IsnHashCtx.LocalAddress.IPv4, LocalIp, LocalIpSize);
} else if (LocalIpSize == sizeof (EFI_IPv6_ADDRESS)) {
CopyMem (&IsnHashCtx.LocalAddress.IPv6, LocalIp, LocalIpSize);
} else {
return EFI_INVALID_PARAMETER; // Unsupported address size
}
//
// Repeat the process for the remote IP address
//
if (RemoteIpSize == sizeof (EFI_IPv4_ADDRESS)) {
CopyMem (&IsnHashCtx.RemoteAddress.IPv4, RemoteIp, RemoteIpSize);
} else if (RemoteIpSize == sizeof (EFI_IPv6_ADDRESS)) {
CopyMem (&IsnHashCtx.RemoteAddress.IPv6, RemoteIp, RemoteIpSize);
} else {
return EFI_INVALID_PARAMETER; // Unsupported address size
}
//
// Compute the hash
// Update the hash with the data
//
Status = Hash2Protocol->HashUpdate (Hash2Protocol, (UINT8 *)&IsnHashCtx, sizeof (IsnHashCtx));
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_NET, "Failed to update hash: %r\n", Status));
return Status;
}
//
// Finalize the hash and retrieve the result
//
Status = Hash2Protocol->HashFinal (Hash2Protocol, &HashResult);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_NET, "Failed to finalize hash: %r\n", Status));
return Status;
}
Status = gRT->GetTime (&TimeStamp, NULL);
if (EFI_ERROR (Status)) {
return Status;
}
//
// copy the first 4 bytes of the hash result into the ISN
//
CopyMem (Isn, HashResult.Md5Hash, sizeof (*Isn));
//
// now add the timestamp to the ISN as 4 microseconds units (1000 / 4 = 250)
//
*Isn += (TCP_SEQNO)TimeStamp.Nanosecond * 250;
return Status;
}
/**
Get the local mss.
@param[in] Sock Pointer to the socket to get mss.
@return The mss size.
**/
UINT16
TcpGetRcvMss (
IN SOCKET *Sock
)
{
EFI_IP4_MODE_DATA Ip4Mode;
EFI_IP6_MODE_DATA Ip6Mode;
EFI_IP4_PROTOCOL *Ip4;
EFI_IP6_PROTOCOL *Ip6;
TCP_PROTO_DATA *TcpProto;
ASSERT (Sock != NULL);
ZeroMem (&Ip4Mode, sizeof (EFI_IP4_MODE_DATA));
ZeroMem (&Ip6Mode, sizeof (EFI_IP6_MODE_DATA));
TcpProto = (TCP_PROTO_DATA *)Sock->ProtoReserved;
if (Sock->IpVersion == IP_VERSION_4) {
Ip4 = TcpProto->TcpService->IpIo->Ip.Ip4;
ASSERT (Ip4 != NULL);
Ip4->GetModeData (Ip4, &Ip4Mode, NULL, NULL);
return (UINT16)(Ip4Mode.MaxPacketSize - sizeof (TCP_HEAD));
} else {
Ip6 = TcpProto->TcpService->IpIo->Ip.Ip6;
ASSERT (Ip6 != NULL);
if (!EFI_ERROR (Ip6->GetModeData (Ip6, &Ip6Mode, NULL, NULL))) {
if (Ip6Mode.AddressList != NULL) {
FreePool (Ip6Mode.AddressList);
}
if (Ip6Mode.GroupTable != NULL) {
FreePool (Ip6Mode.GroupTable);
}
if (Ip6Mode.RouteTable != NULL) {
FreePool (Ip6Mode.RouteTable);
}
if (Ip6Mode.NeighborCache != NULL) {
FreePool (Ip6Mode.NeighborCache);
}
if (Ip6Mode.PrefixTable != NULL) {
FreePool (Ip6Mode.PrefixTable);
}
if (Ip6Mode.IcmpTypeList != NULL) {
FreePool (Ip6Mode.IcmpTypeList);
}
}
return (UINT16)(Ip6Mode.MaxPacketSize - sizeof (TCP_HEAD));
}
}
/**
Set the Tcb's state.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in] State The state to be set.
**/
VOID
TcpSetState (
IN TCP_CB *Tcb,
IN UINT8 State
)
{
ASSERT (Tcb->State < (sizeof (mTcpStateName) / sizeof (CHAR16 *)));
ASSERT (State < (sizeof (mTcpStateName) / sizeof (CHAR16 *)));
DEBUG (
(DEBUG_NET,
"Tcb (%p) state %s --> %s\n",
Tcb,
mTcpStateName[Tcb->State],
mTcpStateName[State])
);
Tcb->State = State;
switch (State) {
case TCP_ESTABLISHED:
SockConnEstablished (Tcb->Sk);
if (Tcb->Parent != NULL) {
//
// A new connection is accepted by a listening socket. Install
// the device path.
//
TcpInstallDevicePath (Tcb->Sk);
}
break;
case TCP_CLOSED:
SockConnClosed (Tcb->Sk);
break;
default:
break;
}
}
/**
Compute the TCP segment's checksum.
@param[in] Nbuf Pointer to the buffer that contains the TCP segment.
@param[in] HeadSum The checksum value of the fixed part of pseudo header.
@return The checksum value.
**/
UINT16
TcpChecksum (
IN NET_BUF *Nbuf,
IN UINT16 HeadSum
)
{
UINT16 Checksum;
Checksum = NetbufChecksum (Nbuf);
Checksum = NetAddChecksum (Checksum, HeadSum);
Checksum = NetAddChecksum (
Checksum,
HTONS ((UINT16)Nbuf->TotalSize)
);
return (UINT16)(~Checksum);
}
/**
Translate the information from the head of the received TCP
segment Nbuf contents and fill it into a TCP_SEG structure.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
@param[in, out] Nbuf Pointer to the buffer contains the TCP segment.
@return Pointer to the TCP_SEG that contains the translated TCP head information.
**/
TCP_SEG *
TcpFormatNetbuf (
IN TCP_CB *Tcb,
IN OUT NET_BUF *Nbuf
)
{
TCP_SEG *Seg;
TCP_HEAD *Head;
Seg = TCPSEG_NETBUF (Nbuf);
Head = (TCP_HEAD *)NetbufGetByte (Nbuf, 0, NULL);
ASSERT (Head != NULL);
Nbuf->Tcp = Head;
Seg->Seq = NTOHL (Head->Seq);
Seg->Ack = NTOHL (Head->Ack);
Seg->End = Seg->Seq + (Nbuf->TotalSize - (Head->HeadLen << 2));
Seg->Urg = NTOHS (Head->Urg);
Seg->Wnd = (NTOHS (Head->Wnd) << Tcb->SndWndScale);
Seg->Flag = Head->Flag;
//
// SYN and FIN flag occupy one sequence space each.
//
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
//
// RFC requires that the initial window not be scaled.
//
Seg->Wnd = NTOHS (Head->Wnd);
Seg->End++;
}
if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
Seg->End++;
}
return Seg;
}
/**
Initialize an active connection.
@param[in, out] Tcb Pointer to the TCP_CB that wants to initiate a
connection.
@retval EFI_SUCCESS The operation completed successfully
@retval others The underlying functions failed and could not complete the operation
**/
EFI_STATUS
TcpOnAppConnect (
IN OUT TCP_CB *Tcb
)
{
EFI_STATUS Status;
Status = TcpInitTcbLocal (Tcb);
if (EFI_ERROR (Status)) {
return Status;
}
TcpSetState (Tcb, TCP_SYN_SENT);
TcpSetTimer (Tcb, TCP_TIMER_CONNECT, Tcb->ConnectTimeout);
TcpToSendData (Tcb, 1);
return EFI_SUCCESS;
}
/**
Initiate the connection close procedure, called when
applications want to close the connection.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
**/
VOID
TcpOnAppClose (
IN OUT TCP_CB *Tcb
)
{
ASSERT (Tcb != NULL);
if (!IsListEmpty (&Tcb->RcvQue) || (GET_RCV_DATASIZE (Tcb->Sk) != 0)) {
DEBUG (
(DEBUG_WARN,
"TcpOnAppClose: connection reset because data is lost for TCB %p\n",
Tcb)
);
TcpResetConnection (Tcb);
TcpClose (Tcb);
return;
}
switch (Tcb->State) {
case TCP_CLOSED:
case TCP_LISTEN:
case TCP_SYN_SENT:
TcpSetState (Tcb, TCP_CLOSED);
break;
case TCP_SYN_RCVD:
case TCP_ESTABLISHED:
TcpSetState (Tcb, TCP_FIN_WAIT_1);
break;
case TCP_CLOSE_WAIT:
TcpSetState (Tcb, TCP_LAST_ACK);
break;
default:
break;
}
TcpToSendData (Tcb, 1);
}
/**
Check whether the application's newly delivered data can be sent out.
@param[in, out] Tcb Pointer to the TCP_CB of this TCP instance.
@retval 0 The data has been sent out successfully.
@retval -1 The Tcb is not in a state that data is permitted to
be sent out.
**/
INTN
TcpOnAppSend (
IN OUT TCP_CB *Tcb
)
{
switch (Tcb->State) {
case TCP_CLOSED:
return -1;
case TCP_LISTEN:
return -1;
case TCP_SYN_SENT:
case TCP_SYN_RCVD:
return 0;
case TCP_ESTABLISHED:
case TCP_CLOSE_WAIT:
TcpToSendData (Tcb, 0);
return 0;
case TCP_FIN_WAIT_1:
case TCP_FIN_WAIT_2:
case TCP_CLOSING:
case TCP_LAST_ACK:
case TCP_TIME_WAIT:
return -1;
default:
break;
}
return 0;
}
/**
Application has consumed some data. Check whether
to send a window update ack or a delayed ack.
@param[in] Tcb Pointer to the TCP_CB of this TCP instance.
**/
VOID
TcpOnAppConsume (
IN TCP_CB *Tcb
)
{
UINT32 TcpOld;
switch (Tcb->State) {
case TCP_ESTABLISHED:
TcpOld = TcpRcvWinOld (Tcb);
if (TcpRcvWinNow (Tcb) > TcpOld) {
if (TcpOld < Tcb->RcvMss) {
DEBUG (
(DEBUG_NET,
"TcpOnAppConsume: send a window update for a window closed Tcb %p\n",
Tcb)
);
TcpSendAck (Tcb);
} else if (Tcb->DelayedAck == 0) {
DEBUG (
(DEBUG_NET,
"TcpOnAppConsume: scheduled a delayed ACK to update window for Tcb %p\n",
Tcb)
);
Tcb->DelayedAck = 1;
}
}
break;
default:
break;
}
}
/**
Abort the connection by sending a reset segment. Called
when the application wants to abort the connection.
@param[in] Tcb Pointer to the TCP_CB of the TCP instance.
**/
VOID
TcpOnAppAbort (
IN TCP_CB *Tcb
)
{
DEBUG (
(DEBUG_WARN,
"TcpOnAppAbort: connection reset issued by application for TCB %p\n",
Tcb)
);
switch (Tcb->State) {
case TCP_SYN_RCVD:
case TCP_ESTABLISHED:
case TCP_FIN_WAIT_1:
case TCP_FIN_WAIT_2:
case TCP_CLOSE_WAIT:
TcpResetConnection (Tcb);
break;
default:
break;
}
TcpSetState (Tcb, TCP_CLOSED);
}
/**
Reset the connection related with Tcb.
@param[in] Tcb Pointer to the TCP_CB of the connection to be reset.
**/
VOID
TcpResetConnection (
IN TCP_CB *Tcb
)
{
NET_BUF *Nbuf;
TCP_HEAD *Nhead;
Nbuf = NetbufAlloc (TCP_MAX_HEAD);
if (Nbuf == NULL) {
return;
}
Nhead = (TCP_HEAD *)NetbufAllocSpace (
Nbuf,
sizeof (TCP_HEAD),
NET_BUF_TAIL
);
ASSERT (Nhead != NULL);
Nbuf->Tcp = Nhead;
Nhead->Flag = TCP_FLG_RST;
Nhead->Seq = HTONL (Tcb->SndNxt);
Nhead->Ack = HTONL (Tcb->RcvNxt);
Nhead->SrcPort = Tcb->LocalEnd.Port;
Nhead->DstPort = Tcb->RemoteEnd.Port;
Nhead->HeadLen = (UINT8)(sizeof (TCP_HEAD) >> 2);
Nhead->Res = 0;
Nhead->Wnd = HTONS (0xFFFF);
Nhead->Checksum = 0;
Nhead->Urg = 0;
Nhead->Checksum = TcpChecksum (Nbuf, Tcb->HeadSum);
TcpSendIpPacket (Tcb, Nbuf, &Tcb->LocalEnd.Ip, &Tcb->RemoteEnd.Ip, Tcb->Sk->IpVersion);
NetbufFree (Nbuf);
}
/**
Install the device path protocol on the TCP instance.
@param[in] Sock Pointer to the socket representing the TCP instance.
@retval EFI_SUCCESS The device path protocol was installed.
@retval other Failed to install the device path protocol.
**/
EFI_STATUS
TcpInstallDevicePath (
IN SOCKET *Sock
)
{
TCP_PROTO_DATA *TcpProto;
TCP_SERVICE_DATA *TcpService;
TCP_CB *Tcb;
IPv4_DEVICE_PATH Ip4DPathNode;
IPv6_DEVICE_PATH Ip6DPathNode;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
EFI_STATUS Status;
TCP_PORTNO LocalPort;
TCP_PORTNO RemotePort;
TcpProto = (TCP_PROTO_DATA *)Sock->ProtoReserved;
TcpService = TcpProto->TcpService;
Tcb = TcpProto->TcpPcb;
LocalPort = NTOHS (Tcb->LocalEnd.Port);
RemotePort = NTOHS (Tcb->RemoteEnd.Port);
if (Sock->IpVersion == IP_VERSION_4) {
NetLibCreateIPv4DPathNode (
&Ip4DPathNode,
TcpService->ControllerHandle,
Tcb->LocalEnd.Ip.Addr[0],
LocalPort,
Tcb->RemoteEnd.Ip.Addr[0],
RemotePort,
EFI_IP_PROTO_TCP,
Tcb->UseDefaultAddr
);
IP4_COPY_ADDRESS (&Ip4DPathNode.SubnetMask, &Tcb->SubnetMask);
DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)&Ip4DPathNode;
} else {
NetLibCreateIPv6DPathNode (
&Ip6DPathNode,
TcpService->ControllerHandle,
&Tcb->LocalEnd.Ip.v6,
LocalPort,
&Tcb->RemoteEnd.Ip.v6,
RemotePort,
EFI_IP_PROTO_TCP
);
DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)&Ip6DPathNode;
}
Sock->DevicePath = AppendDevicePathNode (Sock->ParentDevicePath, DevicePath);
if (Sock->DevicePath == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gBS->InstallProtocolInterface (
&Sock->SockHandle,
&gEfiDevicePathProtocolGuid,
EFI_NATIVE_INTERFACE,
Sock->DevicePath
);
if (EFI_ERROR (Status)) {
FreePool (Sock->DevicePath);
Sock->DevicePath = NULL;
}
return Status;
}