/** @file
Misc support routines for TCP driver.
Copyright (c) 2009 - 2011, 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"
LIST_ENTRY mTcpRunQue = {
&mTcpRunQue,
&mTcpRunQue
};
LIST_ENTRY mTcpListenQue = {
&mTcpListenQue,
&mTcpListenQue
};
TCP_SEQNO mTcpGlobalIss = TCP_BASE_ISS;
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.
**/
VOID
TcpInitTcbLocal (
IN OUT TCP_CB *Tcb
)
{
//
// 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
);
} else {
Tcb->HeadSum = NetIp6PseudoHeadChecksum (
&Tcb->LocalEnd.Ip.v6,
&Tcb->RemoteEnd.Ip.v6,
0x06,
0
);
}
Tcb->Iss = TcpGetIss ();
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->ProbeTimerOn = FALSE;
}
/**
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 intial 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);
//
// 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;
TCP_PROTO_DATA *TcpProto;
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);
TcpProto = (TCP_PROTO_DATA *) Tcb->Sk->ProtoReserved;
TcpSetVariableData (TcpProto->TcpService);
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 ((EFI_D_ERROR, "TcpCloneTcb: failed to clone a sock\n"));
FreePool (Clone);
return NULL;
}
((TCP_PROTO_DATA *) (Clone->Sk->ProtoReserved))->TcpPcb = Clone;
return Clone;
}
/**
Compute an ISS to be used by a new connection.
@return The resulting ISS.
**/
TCP_SEQNO
TcpGetIss (
VOID
)
{
mTcpGlobalIss += TCP_ISS_INCREMENT_1;
return mTcpGlobalIss;
}
/**
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);
Ip6->GetModeData (Ip6, &Ip6Mode, NULL, NULL);
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 (
(EFI_D_INFO,
"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.
**/
VOID
TcpOnAppConnect (
IN OUT TCP_CB *Tcb
)
{
TcpInitTcbLocal (Tcb);
TcpSetState (Tcb, TCP_SYN_SENT);
TcpSetTimer (Tcb, TCP_TIMER_CONNECT, Tcb->ConnectTimeout);
TcpToSendData (Tcb, 1);
}
/**
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 (
(EFI_D_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 (
(EFI_D_INFO,
"TcpOnAppConsume: send a window update for a window closed Tcb %p\n",
Tcb)
);
TcpSendAck (Tcb);
} else if (Tcb->DelayedAck == 0) {
DEBUG (
(EFI_D_INFO,
"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 (
(EFI_D_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);
}
/**
Set the Tcp variable data.
@param[in] TcpService Tcp service data.
@retval EFI_OUT_OF_RESOURCES There are not enough resources to set the variable.
@retval other Set variable failed.
**/
EFI_STATUS
TcpSetVariableData (
IN TCP_SERVICE_DATA *TcpService
)
{
EFI_GUID *ServiceBindingGuid;
UINT32 NumConfiguredInstance;
LIST_ENTRY *Entry;
TCP_CB *TcpPcb;
TCP_PROTO_DATA *TcpProto;
UINTN VariableDataSize;
EFI_TCP4_VARIABLE_DATA *Tcp4VariableData;
EFI_TCP4_SERVICE_POINT *Tcp4ServicePoint;
EFI_TCP6_VARIABLE_DATA *Tcp6VariableData;
EFI_TCP6_SERVICE_POINT *Tcp6ServicePoint;
VOID *VariableData;
CHAR16 *NewMacString;
EFI_STATUS Status;
if (TcpService->IpVersion == IP_VERSION_4) {
ServiceBindingGuid = &gEfiTcp4ServiceBindingProtocolGuid;
} else {
ServiceBindingGuid = &gEfiTcp6ServiceBindingProtocolGuid;
}
NumConfiguredInstance = 0;
Tcp4VariableData = NULL;
Tcp6VariableData = NULL;
//
// Go through the running queue to count the instances.
//
NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
TcpProto = (TCP_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;
if (TcpProto->TcpService == TcpService) {
//
// This tcp instance belongs to the TcpService.
//
NumConfiguredInstance++;
}
}
//
// Go through the listening queue to count the instances.
//
NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
TcpProto = (TCP_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;
if (TcpProto->TcpService == TcpService) {
//
// This tcp instance belongs to the TcpService.
//
NumConfiguredInstance++;
}
}
Tcp4ServicePoint = NULL;
Tcp6ServicePoint = NULL;
//
// Calculate the size of the Tcp4VariableData. As there may be no Tcp4 child,
// we should add extra buffers for the service points only if the number of configured
// children is more than one.
//
if (TcpService->IpVersion == IP_VERSION_4) {
VariableDataSize = sizeof (EFI_TCP4_VARIABLE_DATA);
if (NumConfiguredInstance > 1) {
VariableDataSize += sizeof (EFI_TCP4_SERVICE_POINT) * (NumConfiguredInstance - 1);
}
Tcp4VariableData = AllocateZeroPool (VariableDataSize);
if (Tcp4VariableData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Tcp4VariableData->DriverHandle = TcpService->DriverBindingHandle;
Tcp4VariableData->ServiceCount = NumConfiguredInstance;
Tcp4ServicePoint = &Tcp4VariableData->Services[0];
VariableData = Tcp4VariableData;
} else {
VariableDataSize = sizeof (EFI_TCP6_VARIABLE_DATA);
if (NumConfiguredInstance > 1) {
VariableDataSize += sizeof (EFI_TCP6_SERVICE_POINT) * (NumConfiguredInstance - 1);
}
Tcp6VariableData = AllocateZeroPool (VariableDataSize);
if (Tcp6VariableData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Tcp6VariableData->DriverHandle = TcpService->DriverBindingHandle;
Tcp6VariableData->ServiceCount = NumConfiguredInstance;
Tcp6ServicePoint = &Tcp6VariableData->Services[0];
VariableData = Tcp6VariableData;
}
//
// Go through the running queue to fill the service points.
//
NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
TcpProto = (TCP_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;
if (TcpProto->TcpService == TcpService) {
//
// This tcp instance belongs to the TcpService.
//
if (TcpService->IpVersion == IP_VERSION_4) {
Tcp4ServicePoint->InstanceHandle = TcpPcb->Sk->SockHandle;
CopyMem (&Tcp4ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
Tcp4ServicePoint->LocalPort = NTOHS (TcpPcb->LocalEnd.Port);
CopyMem (&Tcp4ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
Tcp4ServicePoint->RemotePort = NTOHS (TcpPcb->RemoteEnd.Port);
Tcp4ServicePoint++;
} else {
Tcp6ServicePoint->InstanceHandle = TcpPcb->Sk->SockHandle;
IP6_COPY_ADDRESS (&Tcp6ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip);
Tcp6ServicePoint->LocalPort = NTOHS (TcpPcb->LocalEnd.Port);
IP6_COPY_ADDRESS (&Tcp6ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip);
Tcp6ServicePoint->RemotePort = NTOHS (TcpPcb->RemoteEnd.Port);
Tcp6ServicePoint++;
}
}
}
//
// Go through the listening queue to fill the service points.
//
NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);
TcpProto = (TCP_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;
if (TcpProto->TcpService == TcpService) {
//
// This tcp instance belongs to the TcpService.
//
if (TcpService->IpVersion == IP_VERSION_4) {
Tcp4ServicePoint->InstanceHandle = TcpPcb->Sk->SockHandle;
CopyMem (&Tcp4ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
Tcp4ServicePoint->LocalPort = NTOHS (TcpPcb->LocalEnd.Port);
CopyMem (&Tcp4ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
Tcp4ServicePoint->RemotePort = NTOHS (TcpPcb->RemoteEnd.Port);
Tcp4ServicePoint++;
} else {
Tcp6ServicePoint->InstanceHandle = TcpPcb->Sk->SockHandle;
IP6_COPY_ADDRESS (&Tcp6ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip);
Tcp6ServicePoint->LocalPort = NTOHS (TcpPcb->LocalEnd.Port);
IP6_COPY_ADDRESS (&Tcp6ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip);
Tcp6ServicePoint->RemotePort = NTOHS (TcpPcb->RemoteEnd.Port);
Tcp6ServicePoint++;
}
}
}
//
// Get the mac string.
//
Status = NetLibGetMacString (
TcpService->ControllerHandle,
TcpService->DriverBindingHandle,
&NewMacString
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
if (TcpService->MacString != NULL) {
//
// The variable is set already. We're going to update it.
//
if (StrCmp (TcpService->MacString, NewMacString) != 0) {
//
// The mac address is changed. Delete the previous variable first.
//
gRT->SetVariable (
TcpService->MacString,
ServiceBindingGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS,
0,
NULL
);
}
FreePool (TcpService->MacString);
}
TcpService->MacString = NewMacString;
Status = gRT->SetVariable (
TcpService->MacString,
ServiceBindingGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS,
VariableDataSize,
VariableData
);
ON_ERROR:
FreePool (VariableData);
return Status;
}
/**
Clear the variable and free the resource.
@param[in] TcpService Tcp service data.
**/
VOID
TcpClearVariableData (
IN TCP_SERVICE_DATA *TcpService
)
{
EFI_GUID *ServiceBindingGuid;
ASSERT (TcpService->MacString != NULL);
if (TcpService->IpVersion == IP_VERSION_4) {
ServiceBindingGuid = &gEfiTcp4ServiceBindingProtocolGuid;
} else {
ServiceBindingGuid = &gEfiTcp6ServiceBindingProtocolGuid;
}
gRT->SetVariable (
TcpService->MacString,
ServiceBindingGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS,
0,
NULL
);
FreePool (TcpService->MacString);
TcpService->MacString = NULL;
}
/**
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;
}