【UEFI基础】EDK网络框架(TCP4)

TCP4

TCP4协议说明

相比UDP4,TCP4是一种面向连接的通信协议,因此有更好的可靠性。

TCP4的首部格式如下:

【UEFI基础】EDK网络框架(TCP4)_第1张图片

各个参数说明如下:

字段 长度(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的连接过程大致如下:

【UEFI基础】EDK网络框架(TCP4)_第2张图片

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网络协议栈中的关系图:

支持
提供
支持
支持
提供
支持
提供
提供
提供
支持
提供
提供
支持
支持
提供
提供
提供
支持
提供
提供
gEfiPciIoProtocolGuid
UNDI
gEfiNetworkInterfaceIdentifierProtocolGuid_31
gEfiDevicePathProtocolGuid
SNP
gEfiSimpleNetworkProtocolGuid
MNP
gEfiVlanConfigProtocolGuid
gEfiManagedNetworkServiceBindingProtocolGuid
gEfiManagedNetworkProtocolGuid
ARP
gEfiArpServiceBindingProtocolGuid
gEfiArpProtocolGuid
IP4
gEfiIp4ServiceBindingProtocolGuid
gEfiIp4Config2ProtocolGuid
gEfiIp4ProtocolGuid
UDP4
gEfiTcp4ServiceBindingProtocolGuid
gEfiTcp4ProtocolGuid

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(),其调用流程:

TcpServiceBindingCreateChild
SockCreateChild
PktRcvdNotify
TcpRxCallback
TcpInput
TcpCloneTcb
SockClone
SockCreate

左边的PktRcvdNotify是IP4中的回调函数,右边就是常用的创建子项的函数。

SOCKET中的主要成员说明如下:

  • SockHandleNetProtocol:这两个参数需要一起看,它们的初始化位于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);
  }

我们需要关注的是gEfiTcp4ProtocolGuidEFI_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;

流格式套接字也叫“面向连接的套接字”,它有以下的特征:

  1. 数据在传输过程中不会消失;
  2. 数据是按照顺序传输的;
  3. 数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。

数据报格式套接字也叫“无连接的套接字”,它有以下的特征:

  1. 强调快速传输而非传输顺序;
  2. 传输的数据可能丢失也可能损毁;
  3. 限制每次传输的数据大小;
  4. 数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
  • 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协议说明可以找到它们的说明。

  • SndBufferRcvBuffer:收发数据使用的缓存。
  • SockError:Socket的状态。
  • BackLog:表示Socket连接数上限。
  • ConnCnt:表示当前的Socket连接数。
  • Parent:它的类型也是SOCKET,从这里可以看出来Socket之间也有父子关系。从前面的调用流程可以看到,Socket可以通过SockCreate()函数创建,而后者又由两个函数调用:
SockCreateChild
SockCreate
SockClone

它们对应的入参是不同的,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维护的连接。
  • ListenTokenListRcvTokenListSndTokenListProcessingSndTokenList:处理收发数据的Token列表。
  • ConnectionToken:Socket连接后调用的Token。
  • CloseToken:Socket关闭时调用的Token。
  • ProtoHandlerProtoReserved: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
  • CreateCallbackDestroyCallbackContext:对应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.)来与它交互。

其运行结果如下:

【UEFI基础】EDK网络框架(TCP4)_第3张图片

这里接收数据并打印出来,所以能够在右边Shell下看到左边程序传递过来的数据。

注意这里的Address(192.168.3.128)和Port(1234)是硬编码的,需要根据实际情况修改。

你可能感兴趣的:(UEFI开发基础,网络,uefi)