TCP4
TCP4协议说明
相比UDP4,TCP4是一种面向连接的通信协议,因此有更好的可靠性。
TCP4的首部格式如下:

各个参数说明如下:
| 字段 | 长度(bit) | 含义 | 
|---|---|---|
| Source Port | 16 | 源端口,标识哪个应用程序发送。 | 
| Destination Port | 16 | 目的端口,标识哪个应用程序接收。 | 
| Sequence Number | 32 | 序号字段。 TCP链接中传输的数据流中每个字节都编上一个序号。 序号字段的值指的是本报文段所发送的数据的第一个字节的序号。 | 
| Acknowledgment Number | 32 | 确认号。 是期望收到对方的下一个报文段的数据的第1个字节的序号。 即上次已成功接收到的数据字节序号加1。 只有ACK标识为1,此字段有效。 | 
| Data Offset | 4 | 数据偏移,即首部长度。 指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。 以32比特(4字节)为计算单位。 最多有60字节的首部,若无选项字段,正常为20字节。 | 
| Reserved | 6 | 保留,必须填0。 | 
| URG | 1 | 紧急指针有效标识。 它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。 | 
| ACK | 1 | 确认序号有效标识。 只有当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。 | 
| PSH | 1 | 标识接收方应该尽快将这个报文段交给应用层。 接收到PSH = 1的TCP报文段,应尽快的交付接收应用进程,而不再等待整个缓存都填满了后再向上交付。 | 
| RST | 1 | 重建连接标识。 当RST=1时,表明TCP连接中出现严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立连接。 | 
| SYN | 1 | 同步序号标识,用来发起一个连接。 SYN=1表示这是一个连接请求或连接接受请求。 | 
| FIN | 1 | 发端完成发送任务标识。 用来释放一个连接。 FIN=1表明此报文段的发送端的数据已经发送完毕,并要求释放连接。 | 
| Window | 16 | 窗口:TCP的流量控制。 窗口起始于确认序号字段指明的值,这个值是接收端期望接收的字节数。 窗口最大为65535字节。 | 
| Checksum | 16 | 校验字段,包括TCP首部和TCP数据,是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。 在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。 | 
| Urgent Pointer | 16 | 紧急指针,只有当URG标志置1时紧急指针才有效。 TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。 紧急指针指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)。 | 
| Options | 可变 | 选项字段。 TCP协议最初只规定了一种选项,即最长报文段长度(只包含数据字段,不包括TCP首部),又称为MSS。 MSS告诉对方TCP“我的缓存所能接收的报文段的数据字段的最大长度是MSS个字节”。 新的RFC规定有以下几种选型:选项表结束,空操作,最大报文段长度,窗口扩大因子,时间戳。 * 选项表结束。 * 空操作:没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。 * 最大报文段长度:又称为MSS,只包含数据字段,不包括TCP首部。 * 窗口扩大因子:3字节,其中一个字节表示偏移值S。新的窗口值等于TCP首部中的窗口位数增大到(16+S),相当于把窗口值向左移动S位后获得实际的窗口大小。 * 时间戳:10字节,其中最主要的字段是时间戳值(4字节)和时间戳回送应答字段(4字节)。 | 
| Padding | 可变 | 填充字段,用来补位,使整个首部长度是4字节的整数倍。 | 
| data | 可变 | TCP负载。 | 
对应UEFI中的代码:
typedef UINT32  TCP_SEQNO;
typedef UINT16  TCP_PORTNO;
//
// TCP header definition
//
typedef struct {
  TCP_PORTNO    SrcPort;
  TCP_PORTNO    DstPort;
  TCP_SEQNO     Seq;
  TCP_SEQNO     Ack;
  UINT8         Res     : 4;
  UINT8         HeadLen : 4;
  UINT8         Flag;
  UINT16        Wnd;
  UINT16        Checksum;
  UINT16        Urg;
} TCP_HEAD;
TCP的连接过程大致如下:

TCP4代码综述
TCP4也是一个通用的网络协议,其实现在NetworkPkg\TcpDxe\TcpDxe.inf,这里首先需要看下它的入口:
EFI_STATUS
EFIAPI
TcpDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // Install the TCP Driver Binding Protocol
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gTcp4DriverBinding,
             ImageHandle,
             &gTcpComponentName,
             &gTcpComponentName2
             );
  //
  // Initialize ISS and random port.
  //
  Seed            = NetRandomInitSeed ();
  mTcpGlobalIss   = NET_RANDOM (Seed) % mTcpGlobalIss;
  mTcp4RandomPort = (UINT16)(TCP_PORT_KNOWN + (NET_RANDOM (Seed) % TCP_PORT_KNOWN));
}
因为TCP4也是一个UEFI Driver Model,所以第一步是安装gTcp4DriverBinding,其实现:
EFI_DRIVER_BINDING_PROTOCOL  gTcp4DriverBinding = {
  Tcp4DriverBindingSupported,
  Tcp4DriverBindingStart,
  Tcp4DriverBindingStop,
  0xa,
  NULL,
  NULL
};
而第二步是初始化一个随机的TCP端口,根据通用网络协议的做法,TCP的端口占两个字节(即16位),只要不是0-1023里面的公认端口都可以,且跟UDP端口的一致也没有关系。
最后还有一个mTcpGlobalIss,这里的ISS全称是Initial Sending Sequence,它的值本身不是很重要,从名字也知道它的作用就是指定TCP发送的第一个包的序列号,这是因为TCP一次发送的包可能会有很多,所以需要排序。
UDP4在UEFI网络协议栈中的关系图:
Tcp4DriverBindingSupported
TCP4依赖于IP4:
EFI_STATUS
EFIAPI
Tcp4DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  //
  // Test for the Ip4ServiceBinding Protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiIp4ServiceBindingProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  return Status;
}
Tcp4DriverBindingStart
Start函数里面只有一个函数TcpCreateService(),它的作用就是初始化TCP_SERVICE_DATA。
TCP_SERVICE_DATA
该结构体本身也比较简单:
typedef struct _TCP_SERVICE_DATA {
  UINT32                          Signature;
  EFI_HANDLE                      ControllerHandle;
  EFI_HANDLE                      DriverBindingHandle;
  UINT8                           IpVersion;
  IP_IO                           *IpIo;
  EFI_SERVICE_BINDING_PROTOCOL    ServiceBinding;
  LIST_ENTRY                      SocketList;
} TCP_SERVICE_DATA;
其重点其实就是一个SocketList,它对应列表成员是SOCKET。
SOCKET
Socket就是TCP的子项。该结构体如下:
///
/// The socket structure representing a network service access point.
///
struct _TCP_SOCKET {
  //
  // Socket description information
  //
  UINT32                      Signature;     ///< Signature of the socket
  EFI_HANDLE                  SockHandle;    ///< The virtual handle of the socket
  EFI_HANDLE                  DriverBinding; ///< Socket's driver binding protocol
  EFI_DEVICE_PATH_PROTOCOL    *ParentDevicePath;
  EFI_DEVICE_PATH_PROTOCOL    *DevicePath;
  LIST_ENTRY                  Link;
  UINT8                       ConfigureState;
  SOCK_TYPE                   Type;
  UINT8                       State;
  UINT16                      Flag;
  EFI_LOCK                    Lock;         ///< The lock of socket
  SOCK_BUFFER                 SndBuffer;    ///< Send buffer of application's data
  SOCK_BUFFER                 RcvBuffer;    ///< Receive buffer of received data
  EFI_STATUS                  SockError;    ///< The error returned by low layer protocol
  BOOLEAN                     InDestroy;
  //
  // Fields used to manage the connection request
  //
  UINT32                      BackLog;        ///< the limit of connection to this socket
  UINT32                      ConnCnt;        ///< the current count of connections to it
  SOCKET                      *Parent;        ///< listening parent that accept the connection
  LIST_ENTRY                  ConnectionList; ///< the connections maintained by this socket
  //
  // The queue to buffer application's asynchronous token
  //
  LIST_ENTRY                  ListenTokenList;
  LIST_ENTRY                  RcvTokenList;
  LIST_ENTRY                  SndTokenList;
  LIST_ENTRY                  ProcessingSndTokenList;
  SOCK_COMPLETION_TOKEN       *ConnectionToken; ///< app's token to signal if connected
  SOCK_COMPLETION_TOKEN       *CloseToken;      ///< app's token to signal if closed
  //
  // Interface for low level protocol
  //
  SOCK_PROTO_HANDLER          ProtoHandler;                      ///< The request handler of protocol
  UINT8                       ProtoReserved[PROTO_RESERVED_LEN]; ///< Data fields reserved for protocol
  UINT8                       IpVersion;
  NET_PROTOCOL                NetProtocol;                      ///< TCP4 or TCP6 protocol socket used
  //
  // Callbacks after socket is created and before socket is to be destroyed.
  //
  SOCK_CREATE_CALLBACK        CreateCallback;  ///< Callback after created
  SOCK_DESTROY_CALLBACK       DestroyCallback; ///< Callback before destroyed
  VOID                        *Context;        ///< The context of the callback
};
该结构体的创建来自SockCreate(),其调用流程:
左边的PktRcvdNotify是IP4中的回调函数,右边就是常用的创建子项的函数。
SOCKET中的主要成员说明如下:
- SockHandle、- NetProtocol:这两个参数需要一起看,它们的初始化位于- SockCreate()函数中:
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Sock->SockHandle,
                  TcpProtocolGuid,
                  &Sock->NetProtocol,
                  NULL
                  );
事实上就是安装了一个Protocol,对应的GUID是TcpProtocolGuid,它其实就是两个选择,v4和v6,对应到NetProtocol也就有了两个版本:
  if (SockInitData->IpVersion == IP_VERSION_4) {
    TcpProtocolGuid = &gEfiTcp4ProtocolGuid;
    ProtocolLength  = sizeof (EFI_TCP4_PROTOCOL);
  } else {
    TcpProtocolGuid = &gEfiTcp6ProtocolGuid;
    ProtocolLength  = sizeof (EFI_TCP6_PROTOCOL);
  }
我们需要关注的是gEfiTcp4ProtocolGuid和EFI_TCP4_PROTOCOL,后者对应结构体:
struct _EFI_TCP4_PROTOCOL {
  EFI_TCP4_GET_MODE_DATA    GetModeData;
  EFI_TCP4_CONFIGURE        Configure;
  EFI_TCP4_ROUTES           Routes;
  EFI_TCP4_CONNECT          Connect;
  EFI_TCP4_ACCEPT           Accept;
  EFI_TCP4_TRANSMIT         Transmit;
  EFI_TCP4_RECEIVE          Receive;
  EFI_TCP4_CLOSE            Close;
  EFI_TCP4_CANCEL           Cancel;
  EFI_TCP4_POLL             Poll;
};
就是真正用于收发数据的TCP接口。
- DriverBinding:这个值来自- SOCK_INIT_DATA中的- DriverBinding:
Sock->DriverBinding       = SockInitData->DriverBinding;
而后者有来自TCP_SERVICE_DATA中的DriverBindingHandle:
mTcpDefaultSockData.DriverBinding = TcpServiceData->DriverBindingHandle;
所以说到底SOCKET中的DriverBinding就是TCP_SERVICE_DATA中的DriverBindingHandle,最终就是EFI_DRIVER_BINDING_PROTOCOL中的DriverBindingHandle。
- ParentDevicePath:它跟上一个参数是有关联的:
  Status = gBS->OpenProtocol (
                  TcpServiceData->ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&This->ParentDevicePath,
                  TcpServiceData->DriverBindingHandle,
                  This->SockHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
实际上就是代表网卡的设备路径,其值以字符串表示大概是这样的:
PciRoot(0x0)/Pci(0x2,0x0)/MAC(525400123456,0x1)
PCI路径可以不用关注,重点在于到MAC为止。
- DevicePath:它是在- ParentDevicePath之上增加了- IPv4_DEVICE_PATH的结果:
  Sock->DevicePath = AppendDevicePathNode (Sock->ParentDevicePath, DevicePath);
  Status = gBS->InstallProtocolInterface (
                  &Sock->SockHandle,
                  &gEfiDevicePathProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  Sock->DevicePath
                  );
其值以字符串表示大概是这样的:
PciRoot(0x0)/Pci(0x2,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0)
- Link:该值与- TCP_SERVICE_DATA中的- SocketList连接。
- ConfigureState:表示Socket的配置状态,有以下的值:
///
/// Socket configure state
///
#define SO_UNCONFIGURED        0
#define SO_CONFIGURED_ACTIVE   1
#define SO_CONFIGURED_PASSIVE  2
#define SO_NO_MAPPING          3
- Type:Socket有两种类型,分别是流格式套接字和数据报格式套接字,对应如下代码:
///
///  The socket type.
///
typedef enum {
  SockDgram, ///< This socket providing datagram service
  SockStream ///< This socket providing stream service
} SOCK_TYPE;
流格式套接字也叫“面向连接的套接字”,它有以下的特征:
- 数据在传输过程中不会消失;
- 数据是按照顺序传输的;
- 数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。
数据报格式套接字也叫“无连接的套接字”,它有以下的特征:
- 强调快速传输而非传输顺序;
- 传输的数据可能丢失也可能损毁;
- 限制每次传输的数据大小;
- 数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
- State:表示Socket本身的状态,有如下的值:
///
/// Socket state
///
#define SO_CLOSED         0
#define SO_LISTENING      1
#define SO_CONNECTING     2
#define SO_CONNECTED      3
#define SO_DISCONNECTING  4
- Flag:表示TCP头部中的标识,有如下的值:
///
/// Flags in the TCP header
///
#define TCP_FLG_FIN  0x01
#define TCP_FLG_SYN  0x02
#define TCP_FLG_RST  0x04
#define TCP_FLG_PSH  0x08
#define TCP_FLG_ACK  0x10
#define TCP_FLG_URG  0x20
在TCP4协议说明可以找到它们的说明。
- SndBuffer、- RcvBuffer:收发数据使用的缓存。
- SockError:Socket的状态。
- BackLog:表示Socket连接数上限。
- ConnCnt:表示当前的Socket连接数。
- Parent:它的类型也是- SOCKET,从这里可以看出来Socket之间也有父子关系。从前面的调用流程可以看到,Socket可以通过- SockCreate()函数创建,而后者又由两个函数调用:
它们对应的入参是不同的,SockCreateChild()的入参是mTcpDefaultSockData:
SOCK_INIT_DATA  mTcpDefaultSockData = {
  SockStream,
  SO_CLOSED,
  NULL,	// Parent
  TCP_BACKLOG,
  TCP_SND_BUF_SIZE,
  TCP_RCV_BUF_SIZE,
  IP_VERSION_4,
  NULL,
  TcpCreateSocketCallback,
  TcpDestroySocketCallback,
  NULL,
  NULL,
  0,
  TcpDispatcher,
  NULL,
};
SockClone()的实现:
SOCKET *
SockClone (
  IN SOCKET  *Sock
  )
{
  SOCKET          *ClonedSock;
  SOCK_INIT_DATA  InitData;
  InitData.BackLog         = Sock->BackLog;
  InitData.Parent          = Sock;	// 注意这里的Parent
  InitData.State           = Sock->State;
  InitData.ProtoHandler    = Sock->ProtoHandler;
  InitData.Type            = Sock->Type;
  InitData.RcvBufferSize   = Sock->RcvBuffer.HighWater;
  InitData.SndBufferSize   = Sock->SndBuffer.HighWater;
  InitData.DriverBinding   = Sock->DriverBinding;
  InitData.IpVersion       = Sock->IpVersion;
  InitData.Protocol        = &(Sock->NetProtocol);
  InitData.CreateCallback  = Sock->CreateCallback;
  InitData.DestroyCallback = Sock->DestroyCallback;
  InitData.Context         = Sock->Context;
  InitData.ProtoData       = Sock->ProtoReserved;
  InitData.DataSize        = sizeof (Sock->ProtoReserved);
  ClonedSock = SockCreate (&InitData);
从这里带出了新的父子关系。实际的测试中发现,SockCreateChild()会在启动中执行,并且Parent的值都是0,而SockClone()会在使用TCP时创建Socket,此时的Parent是一个有效的值。
- ConnectionList:当前Socket维护的连接。
- ListenTokenList、- RcvTokenList、- SndTokenList、- ProcessingSndTokenList:处理收发数据的Token列表。
- ConnectionToken:Socket连接后调用的Token。
- CloseToken:Socket关闭时调用的Token。
- ProtoHandler、- ProtoReserved:Socket请求的回调函数以及对应的入参,回调函数就是- TcpDispatcher(),根据入参会执行不同的操作:
EFI_STATUS
TcpDispatcher (
  IN SOCKET  *Sock,
  IN UINT8   Request,
  IN VOID    *Data    OPTIONAL
  )
{
  switch (Request) {
    case SOCK_POLL:
    case SOCK_CONSUMED:
    case SOCK_SND:
    case SOCK_CLOSE:
    case SOCK_ABORT:
    case SOCK_SNDPUSH:
    case SOCK_SNDURG:
    case SOCK_CONNECT:
    case SOCK_ATTACH:
    case SOCK_FLUSH:
    case SOCK_DETACH:
    case SOCK_CONFIGURE:
    case SOCK_MODE:
    case SOCK_ROUTE:
    default:
  }
}
- IpVersion:这里就是- IP_VERSION_4。
- CreateCallback、- DestroyCallback、- Context:对应- mTcpDefaultSockData中的函数,以及它们的入参。
EFI_TCP4_PROTOCOL
该Protocol的结构体如下:
///
/// The EFI_TCP4_PROTOCOL defines the EFI TCPv4 Protocol child to be used by
/// any network drivers or applications to send or receive data stream.
/// It can either listen on a specified port as a service or actively connected
/// to remote peer as a client. Each instance has its own independent settings,
/// such as the routing table.
///
struct _EFI_TCP4_PROTOCOL {
  EFI_TCP4_GET_MODE_DATA    GetModeData;
  EFI_TCP4_CONFIGURE        Configure;
  EFI_TCP4_ROUTES           Routes;
  EFI_TCP4_CONNECT          Connect;
  EFI_TCP4_ACCEPT           Accept;
  EFI_TCP4_TRANSMIT         Transmit;
  EFI_TCP4_RECEIVE          Receive;
  EFI_TCP4_CLOSE            Close;
  EFI_TCP4_CANCEL           Cancel;
  EFI_TCP4_POLL             Poll;
};
对应的实现在NetworkPkg\TcpDxe\TcpDriver.c:
EFI_TCP4_PROTOCOL  gTcp4ProtocolTemplate = {
  Tcp4GetModeData,
  Tcp4Configure,
  Tcp4Routes,
  Tcp4Connect,
  Tcp4Accept,
  Tcp4Transmit,
  Tcp4Receive,
  Tcp4Close,
  Tcp4Cancel,
  Tcp4Poll
};
相比于其它的网络Protocol,这个稍有不同,它包含了Connect、Accept、Close等TCP常用操作。
后面会介绍这些函数的实现。
Tcp4.Connect
对应的实现是Tcp4Connect,其实现是Socket的连接:
EFI_STATUS
EFIAPI
Tcp4Connect (
  IN EFI_TCP4_PROTOCOL          *This,
  IN EFI_TCP4_CONNECTION_TOKEN  *ConnectionToken
  )
{
  return SockConnect (Sock, ConnectionToken);
}
其它的Tcp4Accept、Tcp4Transmit、Tcp4Receive、Tcp4Close等也都是Socket的操作。
TCP代码示例
TCP的代码示例可以在beni/BeniPkg/DynamicCommand/TestDynamicCommand/TestTcp.c · jiangwei/edk2-beni - 码云 - 开源中国 (gitee.com)中找到,它实际上来自EmbeddedPkg\Drivers\AndroidFastbootTransportTcpDxe\FastbootTransportTcpDxe.inf,这是一个开源的模块,不过在编译过程中会报错,所以这里进行了移植,对应BeniPkg\Dxe\TransportTcpDxe\TcpTransportDxe.inf,而TestTcp.c模块就是调用了这个模块。
它将开启一个TCP服务端,可以通过TCP客户端(这里使用了Packet Sender,来自Packet Sender - Free utility to for sending / receiving of network packets. TCP, UDP, SSL.)来与它交互。
其运行结果如下:

这里接收数据并打印出来,所以能够在右边Shell下看到左边程序传递过来的数据。
注意这里的Address(192.168.3.128)和Port(1234)是硬编码的,需要根据实际情况修改。



















