/** @file Misc support routines for TCP driver. (C) Copyright 2014 Hewlett-Packard Development Company, L.P.
Copyright (c) 2009 - 2017, Intel Corporation. All rights reserved.
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 equals to . @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 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; }