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

IP4

IP4协议说明

IP全称Internet Protocol,它属于网络层,对其下各种类型的数据链路层进行了包装,这样网络层可以跨越不同的数据链路,即使是在不同的数据链路上也能实现两端节点之间的数据包传输。

IP层的主要作用就是“实现终端节点之间的通信”:

在这里插入图片描述

IPv4首部的格式如下:

在这里插入图片描述

各个参数的说明如下:

字段长度(bit)含义
Version44:表示为IPv4;
6:表示为IPv6。
IHL4首部长度,如果不带Option字段,则为20,最长为60,该值限制了记录路由选项。
以4字节为一个单位。
Type of Service8服务类型。只有在有QoS差分服务要求时这个字段才起作用。
Total Length16总长度,整个IP数据报的长度,包括首部和数据之和,单位为字节,最长65535。
总长度必须不超过最大传输单元MTU。
Identification16标识,主机每发一个报文,加1,分片重组时会用到该字段。
Flags3标志位:
Bit 0:保留位,必须为0。
Bit 1:DF(Don’t Fragment),能否分片位,0表示可以分片,1表示不能分片。
Bit 2:MF(More Fragment),表示是否该报文为最后一片,0表示最后一片,1代表后面还有。
Fragment Offset13片偏移:分片重组时会用到该字段。
表示较长的分组在分片后,某片在原分组中的相对位置。以8个字节为偏移单位。
Time to Live8生存时间:可经过的最多路由数,即数据包在网络中可通过的路由器数的最大值。
Protocol8协议:下一层协议。
指出此数据包携带的数据使用何种协议,以便目的主机的IP层将数据部分上交给哪个进程处理。常见值:
0: 保留
1: ICMP, Internet Control Message [RFC792]
2: IGMP, Internet Group Management [RFC1112]
3: GGP, Gateway-to-Gateway [RFC823]
4: IP in IP (encapsulation) [RFC2003]
6: TCP Transmission Control Protocol [RFC793]
17: UDP User Datagram Protocol [RFC768]
20: HMP Host Monitoring Protocol [RFC 869]
27: RDP Reliable Data Protocol [ RFC908 ]
46: RSVP (Reservation Protocol)
47: GRE (General Routing Encapsulation)
50: ESP Encap Security Payload [RFC2406]
51: AH (Authentication Header) [RFC2402]
54: NARP (NBMA Address Resolution Protocol) [RFC1735]
58: IPv6-ICMP (ICMP for IPv6) [RFC1883]
59: IPv6-NoNxt (No Next Header for IPv6) [RFC1883]
60: IPv6-Opts (Destination Options for IPv6) [RFC1883]
89: OSPF (OSPF Version 2) [RFC 1583]
112: VRRP (Virtual Router Redundancy Protocol) [RFC3768]
115: L2TP (Layer Two Tunneling Protocol)
124: ISIS over IPv4
126: CRTP (Combat Radio Transport Protocol)
127: CRUDP (Combat Radio User Protocol)
132: SCTP (Stream Control Transmission Protocol)
136: UDPLite [RFC 3828]
137: MPLS-in-IP [RFC 4023]
Header Checksum16首部检验和,只检验数据包的首部,不检验数据部分。
这里不采用CRC检验码,而采用简单的计算方法。
Source Address32源IP地址。
Destination Address32目的IP地址。
Options可变选项字段,用来支持排错,测量以及安全等措施,内容丰富。
选项字段长度可变,从1字节到40字节不等,取决于所选项的功能。
Padding可变填充字段,全填0。

前面部分构成了UEFI代码中的IP4头部:

//
// The EFI_IP4_HEADER is hard to use because the source and
// destination address are defined as EFI_IPv4_ADDRESS, which
// is a structure. Two structures can't be compared or masked
// directly. This is why there is an internal representation.
//
typedef struct {
  UINT8       HeadLen : 4;
  UINT8       Ver     : 4;
  UINT8       Tos;
  UINT16      TotalLen;
  UINT16      Id;
  UINT16      Fragment;
  UINT8       Ttl;
  UINT8       Protocol;
  UINT16      Checksum;
  IP4_ADDR    Src;
  IP4_ADDR    Dst;
} IP4_HEAD;

IP大致分为三大作用模块,分别是IP地址,路由(最终节点为止的转发)以及IP的分包和组包。

IP地址

IP地址用于在“连接到网络中的所有主机中识别出进行通信的目标地址”,在TCP/IP通信中所有主机或路由器必须设定自己的IP地址。

在这里插入图片描述

IP地址(只关注IPv4)是一个32位的整型。为了方便记忆,一般分成8位一组,分成4组,每组以“.”分隔,再将每组数转换成十进制。

IP地址还由“网络标识”和“主机标识”两部分组成,网络标识必须保证相互连接的每个段(网段)的地址不相重复;主机标识则不允许在同一个网段内重复出现。相同网络标识的IP地址组成同一个网段,而网段具体是那几位由子网掩码确定。子网掩码由两种标识方式,第一种是用一个数字表示,比如下图中的24,表示从头数到第24位(值都是1)表示子网掩码:

在这里插入图片描述

第二种就是通过IP地址来表示子网掩码,从头开始的24个用比特值为1来构成,其它值则是0,这里就是:

11111111 11111111 11111111 00000000

即255.255.255.0。

IP地址还可以按照级别进行分类,分别是A类、B类、C类和D类:

在这里插入图片描述

从这里可以看出来,前面示例图中的192.168.128.x是一个C类地址。对于这种类型,同一个网段内有254个地址(0和1是保留地址)。

广播地址用于在同一个链路中相互连接的主机之间发送数据包。主机标识部分如果是全1,则表示广播地址,还是以前面示例图中为例,192.168.128.255就是一个广播地址。广播地址的IP包会被路由器屏蔽,所以不会到达192.168.128.0/24以外的其它链路上。

多播(也叫组播)用于将包发送给特定组内的所有主机。多播使用D类地址,所以多播地址从首位开始到第4位是“1110”。多播的一些常用地址:

在这里插入图片描述

到这里可以将IP通信分为三类:

  • 单播(unicast):一对一通信。
  • 广播(broadcase):一对所有通信。
  • 多播(multicast):一对一组通信。

路由

路由是指将分组数据发送到最终目标地址的功能。

在这里插入图片描述

发送数据到最终目标地址是通过“跳”(Hop)的方式实现的:

在这里插入图片描述

“跳”指网络中的一个区间,IP包正是在网络中一个个“跳”间被转发的,因此IP路由也叫做多跳路由。

在转发IP数据包时只指定下一个路由器或主机,而不是将最终目标地址为止的所有通路全都指定出来。因为每一跳在转发IP数据包时会指定下一跳的操作,直至包到达最终的目标地址。

在这里插入图片描述

发送数据包时所使用的地址是网络层的地址,即IP地址。然而仅仅有IP地址还不足以实现数据包发送到对端目的地址,在数据发送过程中还需要类似于“指明路由器或主机”的信息,以便真正发往目标地址。

为了将数据包发给目标主机,所有主机(包括路由器)都维护者一张路由表(路由控制表)。该表记录IP数据在下一步应该发给哪个路由器。IP包将根据这个路由表在各个数据链路上传输。

在这里插入图片描述

分包和组包

任何一台主机都有必要对IP包分片(IP Fragmentation)进行相应的处理。分片往往在网络上遇到比较大的报文无法一下子发送出去时才会进行处理。以太网的默认MTU(最大传输单元)1500个字节。

IP相关的协议

实际通信中光有IP协议是不够的,还需要与IP相关的技术才能实现。其中的一部分会在后续进一步说明,这里需要介绍的ICMP和IGMP。它们的实现就本节接收的驱动中。

ICMP和IGMP都是封装在IP报文中的。

ICMP

ICMP全称是Internet Control Message Protocol,它的主要功能包括:确认IP包是否成功送达目标地址;通知在发送过程当中IP包被废弃的具体原因;改善网络设置;等等。其格式如下:

在这里插入图片描述

各个参数的说明如下:

字段长度(字节)含义
Type1报文类型,用来标识报文,Type字段的取值和含义后面会进一步说明。
Code1代码,提供报文类型的进一步信息,Code字段的取值和含义后面会进一步说明。
Checksum2校验和,使用和IP相同的加法校验和算法,但是ICMP校验和仅覆盖ICMP报文。
Message Body可变字段的长度和内容,取决于消息的类型和代码。

ICMP消息类型代码对应表如下:

类型Type代码Code描述
00回显应答(ping应答)
30网络不可达
31主机不可达
32协议不可达
33端口不可达
34需要进行分片但设置不分片比特
35源站选路失败
36目的网络不认识
37目的主机不认识
38源主机被隔离(作废不用)
39目的网络被强制禁止
310目的主机被强制禁止
311由于TOS,网络不可达
312由于TOS,主机不可达
313由于过滤,通信被强制禁止
314主机越权
315优先权中止生效
40源端被关闭
50对网络重定向
51对主机重定向
52对服务类型和网络重定向
53对服务类型和主机重定向
80请求回显(ping请求)
90路由器通告
100路由器请求告
110传输期间生存时间为0
111在数据报组装期间生存时间为0
120坏的IP首部
121缺少必须的选项
130时间戳请求(作废不用)
140时间戳应答(作废不用)
150信息请求(作废不用)
160信息应答(作废不用)
170地址掩码请求
180地址掩码应答

IGMP

IGMP全称是Internet Group Management Protocol,它的主要功能包括:在接受者主机和直接相连的组播路由器之间建立和维护组播成员的关系;在接受者主机和组播路由器之间交互IGMP报文实现组成员管理功能。

IGMP有多个版本,目前BIOS下维护的是IGMPv2,其格式如下:

在这里插入图片描述

各个参数的说明如下:

字段长度(bit)描述
Type8报文类型,有以下几种类型:
0x11:Membership Query,IGMP查询消息。
0x12:Version 1 Membership Report,IGMPv1成员报告消息。
0x16:Version 2 Membership Report,IGMPv2成员报告消息。
0x17:Leave Group,离开消息。
Max Resp Time8在发出响应报告前的以1/10秒为单位的最长时间,缺省值为10秒。
新的最大响应时间(以1/10秒为单位)字段允许查询用路由器为它的查询报文指定准确的查询间隔响应时间。
IGMP版本2主机在随机选择它们的响应时间值时以此作为上限。
这样在查询响应间隔时有助于控制响应的爆发。
Checksum16IGMP消息的校验和。
传送报文时,必须计算校验和并填入该字段中;
接收报文时,必须在处理报文之前检验校验和,以判断IGMP消息在传输过程中是否发生了错误。
Group Address32组播组地址(如果是通用查询则为0.0.0.0)。
除了在通用查询时这一字段置为0.0.0.0外,这一字段和IGMP版本1中的这一字段意义相同。

IP4代码综述

IP4是一个通用的网络协议,IP协议有v4版本和v6版本,本文只介绍v4的版本。其实现在NetworkPkg\Ip4Dxe\Ip4Dxe.inf,这里首先需要看下它的入口:

EFI_STATUS
EFIAPI
Ip4DriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  VOID  *Registration;

  EfiCreateProtocolNotifyEvent (
    &gEfiIpSec2ProtocolGuid,
    TPL_CALLBACK,
    IpSec2InstalledCallback,
    NULL,
    &Registration
    );

  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gIp4DriverBinding,
           ImageHandle,
           &gIp4ComponentName,
           &gIp4ComponentName2
           );
}

安装EFI_DRIVER_BINDING_PROTOCOL是基础,不过在此之前还有一个gEfiIpSec2ProtocolGuid的回调,该Protocol针对IP层的安全方面,不过目前的UEFI并没有支持IPSec协议,所以实际上也不需要特别关注。gIp4DriverBinding如下:

EFI_DRIVER_BINDING_PROTOCOL  gIp4DriverBinding = {
  Ip4DriverBindingSupported,
  Ip4DriverBindingStart,
  Ip4DriverBindingStop,
  0xa,
  NULL,
  NULL
};

IP4驱动的代码相比也其它UEFI网络协议模块要大很多,原因包括:1)需要处理更多的协议,比如ICMP、IGMP等;2)除了传输部分代码,还增加了IP配置部分的代码;3)还有很多关于路由的代码。由于内容太多,本文不会全部介绍到关于ICMP、IGMP和路由的内容。

IP4在UEFI网络协议栈中的关系图:

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

Ip4DriverBindingSupported

IP4依赖于MNPARP

EFI_STATUS
EFIAPI
Ip4DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  //
  // Test for the MNP service binding Protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiManagedNetworkServiceBindingProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );

  //
  // Test for the Arp service binding Protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiArpServiceBindingProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
}

Ip4DriverBindingStart

Start函数的流程大致如下:

  1. 通过Ip4CreateService()函数创建IP4服务,主要的实现就是初始化IP4_SERVICE结构体。
  2. 安装gEfiIp4ServiceBindingProtocolGuidgEfiIp4Config2ProtocolGuid。前者跟其他网络驱动类似,就是一个服务接口;后者用于配置IP。
  3. 获取IP4配置,这些配置存放在变量中,命名是MAC地址对应的字符串,配置对应的结构体是IP4_CONFIG2_INSTANCE,在Ip4CreateService()中有默认的初始化,其流程大致是:
Ip4DriverBindingStart
Ip4CreateService
Ip4Config2InitInstance
Ip4Config2WriteConfigData
  1. IP4配置有不同的类型,根据实际类型,调用EFI_IP4_CONFIG2_PROTOCOLSetData()接口进行处理。
  2. 开始接收数据,此时会开启定时器,定时器也是在Ip4CreateService()中创建的。

所以主要的操作还是在IP4_SERVICE以及与它相关的结构体,它们的关系如下:

在这里插入图片描述

IP4_SERVICE

IP4的服务与MNPARP的服务对应,所以也可以有多个,每个IP4服务对应这样一个结构体。与ARP一样,由于不涉及到硬件,所以开始就是SERVICE结构体,而没有硬件相关的结构体。IP4_SERVICEIp4DriverBindingStart()中初始化,通过Ip4CreateService()完成:

EFI_STATUS
EFIAPI
Ip4DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  Status = Ip4CreateService (ControllerHandle, This->DriverBindingHandle, &IpSb);
}

IP4_SERVICE位于NetworkPkg\Ip4Dxe\Ip4Impl.h,其成员如下:

struct _IP4_SERVICE {
  UINT32                             Signature;
  EFI_SERVICE_BINDING_PROTOCOL       ServiceBinding;
  INTN                               State;

  //
  // List of all the IP instances and interfaces, and default
  // interface and route table and caches.
  //
  UINTN                              NumChildren;
  LIST_ENTRY                         Children;

  LIST_ENTRY                         Interfaces;

  IP4_INTERFACE                      *DefaultInterface;
  IP4_ROUTE_TABLE                    *DefaultRouteTable;

  //
  // Ip reassemble utilities, and IGMP data
  //
  IP4_ASSEMBLE_TABLE                 Assemble;
  IGMP_SERVICE_DATA                  IgmpCtrl;

  //
  // Low level protocol used by this service instance
  //
  EFI_HANDLE                         Image;
  EFI_HANDLE                         Controller;

  EFI_HANDLE                         MnpChildHandle;
  EFI_MANAGED_NETWORK_PROTOCOL       *Mnp;

  EFI_MANAGED_NETWORK_CONFIG_DATA    MnpConfigData;
  EFI_SIMPLE_NETWORK_MODE            SnpMode;

  EFI_EVENT                          Timer;
  EFI_EVENT                          ReconfigCheckTimer;
  EFI_EVENT                          ReconfigEvent;

  BOOLEAN                            Reconfig;

  //
  // Underlying media present status.
  //
  BOOLEAN                            MediaPresent;

  //
  // IPv4 Configuration II Protocol instance
  //
  IP4_CONFIG2_INSTANCE               Ip4Config2Instance;

  CHAR16                             *MacString;

  UINT32                             MaxPacketSize;
  UINT32                             OldMaxPacketSize; ///< The MTU before IPsec enable.
};

这里说明其中比较重要的成员:

  • ServiceBinding:对应IP4的服务接口:
  IpSb->ServiceBinding.CreateChild  = Ip4ServiceBindingCreateChild;
  IpSb->ServiceBinding.DestroyChild = Ip4ServiceBindingDestroyChild;
  • State:IP4服务的状态,有不同的类型,在不同的类型下有不同的操作,目前使用的状态有:
//
// The state of IP4 service. It starts from UNSTARTED. It transits
// to STARTED if autoconfigure is started. If default address is
// configured, it becomes CONFIGED. and if partly destroyed, it goes
// to DESTROY.
//
#define IP4_SERVICE_UNSTARTED  0
#define IP4_SERVICE_STARTED    1
#define IP4_SERVICE_CONFIGED   2
#define IP4_SERVICE_DESTROY    3

IP4_SERVICE_STARTED会在Ip4StartAutoConfig()中设置,后面会进一步介绍。

IP4_SERVICE_CONFIGED会在Ip4Config2SetDefaultAddr()中设置,其流程如下:

Ip4StartAutoConfig
Ip4Config2OnDhcp4Complete
Ip4Config2SetDefaultIf
Ip4Config2SetDefaultAddr
DataItem[Ip4Config2DataTypeManualAddress]
Ip4Config2SetManualAddress

这又回到了Ip4StartAutoConfig()

这些参数涉及的都是IP的配置,后面还会进一步介绍。

  • NumChildrenChildren:IP4的子项,链表对应的是IP4_PROTOCOL,它通过IP4服务的CreateChild()创建,是IP4通信的单位。
  • Interfaces:对应的是IP4_INTERFACE的列表,后面会进一步介绍这个结构体。
  • DefaultInterface:默认的IP4_INTERFACE,通过Ip4CreateInterface()创建。
  • DefaultRouteTable:默认的路由表。
  • Assemble:对应的是IP4_ASSEMBLE_ENTRY的列表,后续会进一步介绍。
  • IgmpCtrl:IGMP全称Internet Group Management Ptotocol,互联网组管理协议,是TCP/IP协议族中负责IPv4组播成员管理的协议。而该成员是用于控制IGMP的结构体IGMP_SERVICE_DATA
  • MnpChildHandleMnp:IP4对应的EFI_MANAGED_NETWORK_PROTOCOL及其Handle。
  • MnpConfigDataMNP配置数据,它的值也是固定的:
  IpSb->MnpConfigData.ReceivedQueueTimeoutValue = 0;
  IpSb->MnpConfigData.TransmitQueueTimeoutValue = 0;
  IpSb->MnpConfigData.ProtocolTypeFilter        = IP4_ETHER_PROTO;
  IpSb->MnpConfigData.EnableUnicastReceive      = TRUE;
  IpSb->MnpConfigData.EnableMulticastReceive    = TRUE;
  IpSb->MnpConfigData.EnableBroadcastReceive    = TRUE;
  IpSb->MnpConfigData.EnablePromiscuousReceive  = FALSE;
  IpSb->MnpConfigData.FlushQueuesOnReset        = TRUE;
  IpSb->MnpConfigData.EnableReceiveTimestamps   = FALSE;
  IpSb->MnpConfigData.DisableBackgroundPolling  = FALSE;
  • SnpModeSNP模式。
  • TimerReconfigCheckTimerIp4AutoReconfigCallBack:IP4需要使用到的事件,后续会进一步介绍。
  • Reconfig:是否需要重新配置的标志。
  • MediaPresent:网线是否连接的标志。
  • Ip4Config2Instance:对应的结构体是IP4_CONFIG2_INSTANCE,IP4也有不同的配置,比如静态、DHCP,该结构体用于处理这些配置,后续会进一步介绍。其初始化:
EFI_STATUS
Ip4CreateService (
  IN  EFI_HANDLE   Controller,
  IN  EFI_HANDLE   ImageHandle,
  OUT IP4_SERVICE  **Service
  )
{
  ZeroMem (&IpSb->Ip4Config2Instance, sizeof (IP4_CONFIG2_INSTANCE));

  Status = Ip4Config2InitInstance (&IpSb->Ip4Config2Instance);

IP4_INTERFACE

不同于前面介绍的网络协议,IP4的服务数据和子项数据中间还有一个IP4_INTERFACE。这是因为IP层的主要作用是终端节点之间的通信,而为了表示一个节点,必须要有IP地址、MAC等来描述。而IP4_INTERFACE的作用主要就是用来描述节点。

IP4_INTERFACEIp4CreateInterface()创建:

IP4_INTERFACE *
Ip4CreateInterface (
  IN  EFI_MANAGED_NETWORK_PROTOCOL  *Mnp,
  IN  EFI_HANDLE                    Controller,
  IN  EFI_HANDLE                    ImageHandle
  )
{
  IP4_INTERFACE            *Interface;
  EFI_SIMPLE_NETWORK_MODE  SnpMode;

  Interface = AllocatePool (sizeof (IP4_INTERFACE));

  Interface->Signature = IP4_INTERFACE_SIGNATURE;
  InitializeListHead (&Interface->Link);
  Interface->RefCnt = 1;

  Interface->Ip         = IP4_ALLZERO_ADDRESS;
  Interface->SubnetMask = IP4_ALLZERO_ADDRESS;
  Interface->Configured = FALSE;

  Interface->Controller = Controller;
  Interface->Image      = ImageHandle;
  Interface->Mnp        = Mnp;
  Interface->Arp        = NULL;
  Interface->ArpHandle  = NULL;

  InitializeListHead (&Interface->ArpQues);
  InitializeListHead (&Interface->SentFrames);

  Interface->RecvRequest = NULL;

  //
  // Get the interface's Mac address and broadcast mac address from SNP
  //
  if (EFI_ERROR (Mnp->GetModeData (Mnp, NULL, &SnpMode))) {
    FreePool (Interface);
    return NULL;
  }

  CopyMem (&Interface->Mac, &SnpMode.CurrentAddress, sizeof (Interface->Mac));
  CopyMem (&Interface->BroadcastMac, &SnpMode.BroadcastAddress, sizeof (Interface->BroadcastMac));
  Interface->HwaddrLen = SnpMode.HwAddressSize;

  InitializeListHead (&Interface->IpInstances);
  Interface->PromiscRecv = FALSE;
}

其调用流程包含两个部分:

  1. 首先是通过Ip4CreateService()创建服务的时候会使用到,不过此过程创建的IP4_INTERFACE会在初始化后赋值给IP4_SERVICE中的DefaultInterface
EFI_STATUS
Ip4CreateService (
  IN  EFI_HANDLE   Controller,
  IN  EFI_HANDLE   ImageHandle,
  OUT IP4_SERVICE  **Service
  )
{
  IpSb->DefaultInterface = Ip4CreateInterface (IpSb->Mnp, Controller, ImageHandle);
}

所以这只是一个默认的版本。

  1. 其次是配置IP的时候会调用Ip4CreateInterface()来创建IP4_INTERFACE

IP4_INTERFACE结构体位于NetworkPkg\Ip4Dxe\Ip4Common.h,包含的成员如下:

//
// Each IP4 instance has its own station address. All the instances
// with the same station address share a single interface structure.
// Each interface has its own ARP child, and shares one MNP child.
// Notice the special cases that DHCP can configure the interface
// with 0.0.0.0/0.0.0.0.
//
struct _IP4_INTERFACE {
  UINT32                          Signature;
  LIST_ENTRY                      Link;
  INTN                            RefCnt;

  //
  // IP address and subnet mask of the interface. It also contains
  // the subnet/net broadcast address for quick access. The fields
  // are invalid if (Configured == FALSE)
  //
  IP4_ADDR                        Ip;
  IP4_ADDR                        SubnetMask;
  IP4_ADDR                        SubnetBrdcast;
  IP4_ADDR                        NetBrdcast;
  BOOLEAN                         Configured;

  //
  // Handle used to create/destroy ARP child. All the IP children
  // share one MNP which is owned by IP service binding.
  //
  EFI_HANDLE                      Controller;
  EFI_HANDLE                      Image;

  EFI_MANAGED_NETWORK_PROTOCOL    *Mnp;
  EFI_ARP_PROTOCOL                *Arp;
  EFI_HANDLE                      ArpHandle;

  //
  // Queues to keep the frames sent and waiting ARP request.
  //
  LIST_ENTRY                      ArpQues;
  LIST_ENTRY                      SentFrames;
  IP4_LINK_RX_TOKEN               *RecvRequest;

  //
  // The interface's MAC and broadcast MAC address.
  //
  EFI_MAC_ADDRESS                 Mac;
  EFI_MAC_ADDRESS                 BroadcastMac;
  UINT32                          HwaddrLen;

  //
  // All the IP instances that have the same IP/SubnetMask are linked
  // together through IpInstances. If any of the instance enables
  // promiscuous receive, PromiscRecv is true.
  //
  LIST_ENTRY                      IpInstances;
  BOOLEAN                         PromiscRecv;
};

它的成员包含几个部分:

  • IpSubnetMaskSubnetBrdcastNetBrdcast:通用的IP地址。
  • Configured:执行Ip4SetAddres()设置IP之后变成TRUE。
  • MnpArp:底层的网络接口。
  • MacBroadcastMacHwaddrLen:MAC地址数据,它们都来自SNP
  CopyMem (&Interface->Mac, &SnpMode.CurrentAddress, sizeof (Interface->Mac));
  CopyMem (&Interface->BroadcastMac, &SnpMode.BroadcastAddress, sizeof (Interface->BroadcastMac));
  Interface->HwaddrLen = SnpMode.HwAddressSize;
  • ArpQues:对应IP4_ARP_QUE的链表,用来处理ARP数据。
  • SentFrames:对应IP4_LINK_TX_TOKEN的链表,在处理发送的数据时会使用。
  • RecvRequest:一个Token,是IP4中用来处理接收到的数据的,其初始化的代码:
EFI_STATUS
Ip4ReceiveFrame (
  IN  IP4_INTERFACE       *Interface,
  IN  IP4_PROTOCOL        *IpInstance       OPTIONAL,
  IN  IP4_FRAME_CALLBACK  CallBack,
  IN  VOID                *Context
  )
{
  Token = Ip4CreateLinkRxToken (Interface, IpInstance, CallBack, Context);	// 构建Token

  Interface->RecvRequest = Token;
  Status                 = Interface->Mnp->Receive (Interface->Mnp, &Token->MnpToken);
}

Ip4CreateLinkRxToken()的实现:

IP4_LINK_RX_TOKEN *
Ip4CreateLinkRxToken (
  IN IP4_INTERFACE       *Interface,
  IN IP4_PROTOCOL        *IpInstance,
  IN IP4_FRAME_CALLBACK  CallBack,
  IN VOID                *Context
  )
{
  Token = AllocatePool (sizeof (IP4_LINK_RX_TOKEN));

  Token->Signature  = IP4_FRAME_RX_SIGNATURE;
  Token->Interface  = Interface;
  Token->IpInstance = IpInstance;
  Token->CallBack   = CallBack;
  Token->Context    = Context;

  MnpToken         = &Token->MnpToken;
  MnpToken->Status = EFI_NOT_READY;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  Ip4OnFrameReceived,
                  Token,
                  &MnpToken->Event
                  );

  MnpToken->Packet.RxData = NULL;
}

Ip4OnFrameReceived()是接收数据的回调函数,其实现也是一个DPC处理:

VOID
EFIAPI
Ip4OnFrameReceived (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Request Ip4OnFrameReceivedDpc as a DPC at TPL_CALLBACK
  //
  QueueDpc (TPL_CALLBACK, Ip4OnFrameReceivedDpc, Context);
}

Ip4OnFrameReceivedDpc()的处理代码中最重要的是Token->CallBack,它来自类似如下的代码中:

Ip4ReceiveFrame (IpIf, NULL, Ip4AccpetFrame, IpSb);

Ip4AccpetFrame中会进一步介绍。

  • IpInstances:对应的IP4_PROTOCOL的链表。在IP4_INTERFACE初始化完成之后只是一个包含IP等地址的网络接口,但是它本身没有收发数据的能力,需要配合IP4_PROTOCOL才能完成数据的收发,而此时的数据就包含了IP4_INTERFACE中的IP地址等信息,构成了一个IP包。

IP4_INTERFACE相当于一个IP4子项对外的网络接口,通过它才能收发IP报文。

IP4_CONFIG2_INSTANCE

IP4_SERVICE中的一个成员Ip4Config2Instance的类型就是IP4_CONFIG2_INSTANCE,一个IP4_SERVICE对应一个IP4_CONFIG2_INSTANCE,它的主要作用是配置IP地址等信息,它包括了真正的处理接口EFI_IP4_CONFIG2_PROTOCOL以及相关的数据。所以为了完成真正的IP配置,就需要依赖于这个结构体。

在初始化IP4_SERVICE的时候会执行Ip4CreateService()函数,该函数初始化了IP4_CONFIG2_INSTANCE结构体:

EFI_STATUS
Ip4CreateService (
  IN  EFI_HANDLE   Controller,
  IN  EFI_HANDLE   ImageHandle,
  OUT IP4_SERVICE  **Service
  )
{
  ZeroMem (&IpSb->Ip4Config2Instance, sizeof (IP4_CONFIG2_INSTANCE));

  Status = Ip4Config2InitInstance (&IpSb->Ip4Config2Instance);

IP4_CONFIG2_INSTANCE结构体位于NetworkPkg\Ip4Dxe\Ip4Config2Impl.h,成员如下:

struct _IP4_CONFIG2_INSTANCE {
  UINT32                            Signature;
  BOOLEAN                           Configured;
  LIST_ENTRY                        Link;
  UINT16                            IfIndex;

  EFI_IP4_CONFIG2_PROTOCOL          Ip4Config2;

  EFI_IP4_CONFIG2_INTERFACE_INFO    InterfaceInfo;
  EFI_IP4_CONFIG2_POLICY            Policy;
  IP4_CONFIG2_DATA_ITEM             DataItem[Ip4Config2DataTypeMaximum];

  EFI_EVENT                         Dhcp4SbNotifyEvent;
  VOID                              *Registration;
  EFI_HANDLE                        Dhcp4Handle;
  EFI_DHCP4_PROTOCOL                *Dhcp4;
  BOOLEAN                           DhcpSuccess;
  BOOLEAN                           OtherInfoOnly;
  EFI_EVENT                         Dhcp4Event;
  UINT32                            FailedIaAddressCount;
  EFI_IPv4_ADDRESS                  *DeclineAddress;
  UINT32                            DeclineAddressCount;

  IP4_FORM_CALLBACK_INFO            CallbackInfo;

  IP4_CONFIG2_NVDATA                Ip4NvData;
};

内容较多,这里只对其中比较重要的进行说明:

  • Configured:执行初始化函数Ip4Config2InitInstance()之后变成TRUE
EFI_STATUS
Ip4Config2InitInstance (
  OUT IP4_CONFIG2_INSTANCE  *Instance
  )
{
  Instance->Configured = TRUE;
}
  • LinkIfIndex:一个网卡可能有多个网口,所以有IfIndex,而Link连接的是IP4_CONFIG2_INSTANCE实例,它会根据IfIndex而有多个,这些实例通过mIp4Config2InstanceList连接。从目前的实现来看,应该只有1个,所以IfIndex的值一直是0。

  • Ip4Config2EFI_IP4_CONFIG2_PROTOCOL实例,其实现:

  Instance->Ip4Config2.SetData              = EfiIp4Config2SetData;
  Instance->Ip4Config2.GetData              = EfiIp4Config2GetData;
  Instance->Ip4Config2.RegisterDataNotify   = EfiIp4Config2RegisterDataNotify;
  Instance->Ip4Config2.UnregisterDataNotify = EfiIp4Config2UnregisterDataNotify;
  • InterfaceInfo:网络配置的参数:
///
/// EFI_IP4_CONFIG2_INTERFACE_INFO
///
typedef struct {
  ///
  /// The name of the interface. It is a NULL-terminated Unicode string.
  ///
  CHAR16              Name[EFI_IP4_CONFIG2_INTERFACE_INFO_NAME_SIZE];
  ///
  /// The interface type of the network interface. See RFC 1700,
  /// section "Number Hardware Type".
  ///
  UINT8               IfType;
  ///
  /// The size, in bytes, of the network interface's hardware address.
  ///
  UINT32              HwAddressSize;
  ///
  /// The hardware address for the network interface.
  ///
  EFI_MAC_ADDRESS     HwAddress;
  ///
  /// The station IPv4 address of this EFI IPv4 network stack.
  ///
  EFI_IPv4_ADDRESS    StationAddress;
  ///
  /// The subnet address mask that is associated with the station address.
  ///
  EFI_IPv4_ADDRESS    SubnetMask;
  ///
  /// Size of the following RouteTable, in bytes. May be zero.
  ///
  UINT32              RouteTableSize;
  ///
  /// The route table of the IPv4 network stack runs on this interface.
  /// Set to NULL if RouteTableSize is zero. Type EFI_IP4_ROUTE_TABLE is defined in
  /// EFI_IP4_PROTOCOL.GetModeData().
  ///
  EFI_IP4_ROUTE_TABLE   *RouteTable     OPTIONAL;
} EFI_IP4_CONFIG2_INTERFACE_INFO;
  • Policy:网络配置的方式,包括静态和动态两种:
///
/// EFI_IP4_CONFIG2_POLICY
///
typedef enum {
  ///
  /// Under this policy, the Ip4Config2DataTypeManualAddress,
  /// Ip4Config2DataTypeGateway and Ip4Config2DataTypeDnsServer configuration
  /// data are required to be set manually. The EFI IPv4 Protocol will get all
  /// required configuration such as IPv4 address, subnet mask and
  /// gateway settings from the EFI IPv4 Configuration II protocol.
  ///
  Ip4Config2PolicyStatic,
  ///
  /// Under this policy, the Ip4Config2DataTypeManualAddress,
  /// Ip4Config2DataTypeGateway and Ip4Config2DataTypeDnsServer configuration data are
  /// not allowed to set via SetData(). All of these configurations are retrieved from DHCP
  /// server or other auto-configuration mechanism.
  ///
  Ip4Config2PolicyDhcp,
  Ip4Config2PolicyMax
} EFI_IP4_CONFIG2_POLICY;
  • DataItem:包含网络参数操作的数组,每个数组成员表示的是一组网络参数操作,其操作有:
///
/// EFI_IP4_CONFIG2_DATA_TYPE
///
typedef enum {
  ///
  /// The interface information of the communication device this EFI
  /// IPv4 Configuration II Protocol instance manages. This type of
  /// data is read only. The corresponding Data is of type
  /// EFI_IP4_CONFIG2_INTERFACE_INFO.
  ///
  Ip4Config2DataTypeInterfaceInfo,
  ///
  /// The general configuration policy for the EFI IPv4 network stack
  /// running on the communication device this EFI IPv4
  /// Configuration II Protocol instance manages. The policy will
  /// affect other configuration settings. The corresponding Data is of
  /// type EFI_IP4_CONFIG2_POLICY.
  ///
  Ip4Config2DataTypePolicy,
  ///
  /// The station addresses set manually for the EFI IPv4 network
  /// stack. It is only configurable when the policy is
  /// Ip4Config2PolicyStatic. The corresponding Data is of
  /// type EFI_IP4_CONFIG2_MANUAL_ADDRESS. When DataSize
  /// is 0 and Data is NULL, the existing configuration is cleared
  /// from the EFI IPv4 Configuration II Protocol instance.
  ///
  Ip4Config2DataTypeManualAddress,
  ///
  /// The gateway addresses set manually for the EFI IPv4 network
  /// stack running on the communication device this EFI IPv4
  /// Configuration II Protocol manages. It is not configurable when
  /// the policy is Ip4Config2PolicyDhcp. The gateway
  /// addresses must be unicast IPv4 addresses. The corresponding
  /// Data is a pointer to an array of EFI_IPv4_ADDRESS instances.
  /// When DataSize is 0 and Data is NULL, the existing configuration
  /// is cleared from the EFI IPv4 Configuration II Protocol instance.
  ///
  Ip4Config2DataTypeGateway,
  ///
  /// The DNS server list for the EFI IPv4 network stack running on
  /// the communication device this EFI IPv4 Configuration II
  /// Protocol manages. It is not configurable when the policy is
  /// Ip4Config2PolicyDhcp. The DNS server addresses must be
  /// unicast IPv4 addresses. The corresponding Data is a pointer to
  /// an array of EFI_IPv4_ADDRESS instances. When DataSize
  /// is 0 and Data is NULL, the existing configuration is cleared
  /// from the EFI IPv4 Configuration II Protocol instance.
  ///
  Ip4Config2DataTypeDnsServer,
  Ip4Config2DataTypeMaximum
} EFI_IP4_CONFIG2_DATA_TYPE;

所以它实际上是一个数组DataItem[Ip4Config2DataTypeMaximum],其中包含了配置需要的大部分操作函数。

  • Dhcp4SbNotifyEventRegistrationDhcp4HandleDhcp4Dhcp4EventDHCP4相关的参数,此时DHCP4还没有安装,所以这里是通过回调的方式来实现的。
  • DhcpSuccessDHCP4初始化成功之后设置为TRUE,来自函数Ip4Config2OnDhcp4Complete(),它是Dhcp4Event事件的回调函数。
  • OtherInfoOnlyFailedIaAddressCountDeclineAddressDeclineAddressCount:当前未使用,它们有一个IPv6的版本,会被使用。
  • CallbackInfo:UI操作的回调函数。
  • Ip4NvData:网络参数:
typedef struct {
  EFI_IP4_CONFIG2_POLICY            Policy;                      ///< manual or automatic
  EFI_IP4_CONFIG2_MANUAL_ADDRESS    *ManualAddress;              ///< IP addresses
  UINT32                            ManualAddressCount;          ///< IP addresses count
  EFI_IPv4_ADDRESS                  *GatewayAddress;             ///< Gateway address
  UINT32                            GatewayAddressCount;         ///< Gateway address count
  EFI_IPv4_ADDRESS                  *DnsAddress;                 ///< DNS server address
  UINT32                            DnsAddressCount;             ///< DNS server address count
} IP4_CONFIG2_NVDATA;

IP配置

IP配置涉及到几个重要的函数,这些函数在前面已经提到过,这里将进一步说明。

首先是Ip4CreateInterface(),它创建了IP4_INTERFACE结构体,这是配置IP的主体。其调用流程如下:

EFI_IP4_PROTOCOL.Configure
EfiIp4Configure
Ip4ConfigProtocol
Ip4CreateInterface
DataItem[Ip4Config2DataTypePolicy]
Ip4Config2SetPolicy
Ip4Config2OnPolicyChanged
Ip4StartAutoConfig
Ip4Config2OnDhcp4Complete
Ip4Config2SetDefaultIf
Ip4Config2SetDefaultAddr
Ip4Config2SetManualAddress
DataItem[Ip4Config2DataTypeManualAddress]

上述几个调用Ip4CreateInterface()的代码可以分为三类,分别对应:

  • EFI_IP4_PROTOCOLConfigure成员函数,后面会进一步介绍。
  • DHCP配置,包括使用DataItem[Ip4Config2DataTypePolicy]Ip4StartAutoConfig()函数,前者实际上已经包含了后者,这可以从后者的调用情况看出来:
回调
注册DPC
回调
ReconfigEvent事件
Ip4AutoReconfigCallBack
Ip4AutoReconfigCallBackDpc
Ip4StartAutoConfig
gEfiDhcp4ServiceBindingProtocolGuid安装
Ip4Config2OnDhcp4SbInstalled
DataItem[Ip4Config2DataTypePolicy]
Ip4Config2SetPolicy
Ip4Config2OnPolicyChanged

这里的第一种调用,ReconfigEvent事件,会在Ip4TimerReconfigChecking中创建,它是一个定时事件,用于检测网络连接是否发生过变化,如果是,且配置策略刚好是DHCP,则可能需要重新进行IP配置。

这里的第二种调用,判断是否安装DHCP4模块,不过这里其实就在Ip4StartAutoConfig()函数中,这是因为IP4和DHCP4执行的前后关系不定,存在IP4模块已经执行而DHCP4模块未执行的情况,因此才有了这样的处理:

VOID
EFIAPI
Ip4Config2OnDhcp4SbInstalled (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  IP4_CONFIG2_INSTANCE  *Instance;

  Instance = (IP4_CONFIG2_INSTANCE *)Context;

  if ((Instance->Dhcp4Handle != NULL) || (Instance->Policy != Ip4Config2PolicyDhcp)) {
    //
    // The DHCP4 child is already created or the policy is no longer DHCP.
    //
    return;
  }

  Ip4StartAutoConfig (Instance);
}

EFI_STATUS
Ip4StartAutoConfig (
  IN IP4_CONFIG2_INSTANCE  *Instance
  )
{
  Status = NetLibCreateServiceChild (
             IpSb->Controller,
             IpSb->Image,
             &gEfiDhcp4ServiceBindingProtocolGuid,
             &Instance->Dhcp4Handle
             );

  if (Status == EFI_UNSUPPORTED) {
    //
    // No DHCPv4 Service Binding protocol, register a notify.
    //
    if (Instance->Dhcp4SbNotifyEvent == NULL) {
      Instance->Dhcp4SbNotifyEvent = EfiCreateProtocolNotifyEvent (
                                       &gEfiDhcp4ServiceBindingProtocolGuid,
                                       TPL_CALLBACK,
                                       Ip4Config2OnDhcp4SbInstalled, // 该函数中还是执行了Ip4StartAutoConfig
                                       (VOID *)Instance,
                                       &Instance->Registration
                                       );
    }
  }
}

这里的第三种调用,使用的是IP4_CONFIG2_INSTANCE中的DataItem,这在前面就已经出现过,DataItem[Ip4Config2DataTypePolicy]对应函数Ip4Config2SetPolicy(),其中有如下的代码:

  //
  // Start the dhcp configuration.
  //
  if (NewPolicy == Ip4Config2PolicyDhcp) {
    Ip4StartAutoConfig (&IpSb->Ip4Config2Instance);
  }

即如果IP配置策略变成了HDCP,则需要执行Ip4StartAutoConfig()函数。

  • 手动配置,使用DataItem[Ip4Config2DataTypeManualAddress]

IP配置示例

IP需要进行配置之后才能够生效,比如Shell下使用ifconfig进行配置:

ifconfig -s eth0 static 192.168.3.128 255.255.255.0 192.168.3.1

它会执行ShellPkg\Library\UefiShellNetwork1CommandsLib\Ifconfig.c中的代码,其流程如下:

ShellCommandRunIfconfig
IfConfig
IfConfigSetInterfaceInfo
获取IP等参数
执行IfCb->IfCfg->SetData配置参数

这里对应的IfCfgEFI_IP4_CONFIG2_PROTOCOLSetData成员函数,其实现是EfiIp4Config2SetData()

EFI_STATUS
EFIAPI
EfiIp4Config2SetData (
  IN EFI_IP4_CONFIG2_PROTOCOL   *This,
  IN EFI_IP4_CONFIG2_DATA_TYPE  DataType,
  IN UINTN                      DataSize,
  IN VOID                       *Data
  )
{
  Status = Instance->DataItem[DataType].Status;
  if (Status != EFI_NOT_READY) {
    if (Instance->DataItem[DataType].SetData == NULL) {
      //
      // This type of data is readonly.
      //
      Status = EFI_WRITE_PROTECTED;
    } else {
      Status = Instance->DataItem[DataType].SetData (Instance, DataSize, Data);
      if (!EFI_ERROR (Status)) {
        //
        // Fire up the events registered with this type of data.
        //
        NetMapIterate (&Instance->DataItem[DataType].EventMap, Ip4Config2SignalEvent, NULL);
        Ip4Config2WriteConfigData (IpSb->MacString, Instance);
      } else if (Status == EFI_ABORTED) {
        //
        // The SetData is aborted because the data to set is the same with
        // the one maintained.
        //
        Status = EFI_SUCCESS;
        NetMapIterate (&Instance->DataItem[DataType].EventMap, Ip4Config2SignalEvent, NULL);
      }
    }
  } else {
    //
    // Another asynchronous process is on the way.
    //
    Status = EFI_ACCESS_DENIED;
  }
}

这里就使用到了IP4_CONFIG2_INSTANCE中的DataItem,无论是手动还是DHCP。

IP4_PROTOCOL

IP4_PROTOCOL是IP4子项使用的数据结构体。注意与EFI_IP4_PROTOCOL的区别,那个是接口,而这个是数据,两者其实是配合使用的,IP4_PROTOCOL就包含了EFI_IP4_PROTOCOL这个成员。它在Ip4ServiceBindingCreateChild()中初始化,这根其它UEFI网络协议的服务接口基本一致:

EFI_STATUS
EFIAPI
Ip4ServiceBindingCreateChild (
  IN EFI_SERVICE_BINDING_PROTOCOL  *This,
  IN OUT EFI_HANDLE                *ChildHandle
  )
{
  IpSb       = IP4_SERVICE_FROM_PROTOCOL (This);
  IpInstance = AllocatePool (sizeof (IP4_PROTOCOL));

  Ip4InitProtocol (IpSb, IpInstance);

  //
  // Install Ip4 onto ChildHandle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  ChildHandle,
                  &gEfiIp4ProtocolGuid,
                  &IpInstance->Ip4Proto,
                  NULL
                  );

  IpInstance->Handle = *ChildHandle;

  //
  // Open the Managed Network protocol BY_CHILD.
  //
  Status = gBS->OpenProtocol (
                  IpSb->MnpChildHandle,
                  &gEfiManagedNetworkProtocolGuid,
                  (VOID **)&Mnp,
                  gIp4DriverBinding.DriverBindingHandle,
                  IpInstance->Handle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );

  InsertTailList (&IpSb->Children, &IpInstance->Link);
  IpSb->NumChildren++;
}

该结构体位于NetworkPkg\Ip4Dxe\Ip4Impl.h:

struct _IP4_PROTOCOL {
  UINT32                 Signature;

  EFI_IP4_PROTOCOL       Ip4Proto;
  EFI_HANDLE             Handle;
  INTN                   State;

  BOOLEAN                InDestroy;

  IP4_SERVICE            *Service;
  LIST_ENTRY             Link;          // Link to all the IP protocol from the service

  //
  // User's transmit/receive tokens, and received/delivered packets
  //
  NET_MAP                RxTokens;
  NET_MAP                TxTokens;      // map between (User's Token, IP4_TXTOKE_WRAP)
  LIST_ENTRY             Received;      // Received but not delivered packet
  LIST_ENTRY             Delivered;     // Delivered and to be recycled packets
  EFI_LOCK               RecycleLock;

  //
  // Instance's address and route tables. There are two route tables.
  // RouteTable is used by the IP4 driver to route packet. EfiRouteTable
  // is used to communicate the current route info to the upper layer.
  //
  IP4_INTERFACE          *Interface;
  LIST_ENTRY             AddrLink;      // Ip instances with the same IP address.
  IP4_ROUTE_TABLE        *RouteTable;

  EFI_IP4_ROUTE_TABLE    *EfiRouteTable;
  UINT32                 EfiRouteCount;

  //
  // IGMP data for this instance
  //
  IP4_ADDR               *Groups;       // stored in network byte order
  UINT32                 GroupCount;

  EFI_IP4_CONFIG_DATA    ConfigData;
};

这里说明其中比较重要的值:

  • Ip4Proto:对应EFI_IP4_PROTOCOL接口。
  • Handle:IP4子项安装EFI_IP4_PROTOCOL对应的Handle。
  • InDestroy:主要在Ip4ServiceBindingDestroyChild()中使用,防止重入,因为数据会在任何时候到来,如果在关于一个IP4子项的时候出现数据,则可能会出现问题,所以需要它来控制。
  • Service:指向IP4_SERVICE
  • Link:在Ip4ServiceBindingCreateChild()创建IP4_PROTOCOL结构体之后会通过该成员放到IP4_SERVICEChildren链表中。
  • RxTokensTxTokensReceivedDelivered:数据处理的结构体。
  • Interface:指向IP4_INTERFACE实例。
  • AddrLink:具有相同IP配置的IP4_INTERFACE链表。
  • RouteTableEfiRouteTable:路由表相关的参数。
  • Groups:指向IP4_ADDR
  • ConfigData:配置参数,其结构体如下:
typedef struct {
  ///
  /// The default IPv4 protocol packets to send and receive. Ignored
  /// when AcceptPromiscuous is TRUE.
  ///
  UINT8               DefaultProtocol;
  ///
  /// Set to TRUE to receive all IPv4 packets that get through the receive filters.
  /// Set to FALSE to receive only the DefaultProtocol IPv4
  /// packets that get through the receive filters.
  ///
  BOOLEAN             AcceptAnyProtocol;
  ///
  /// Set to TRUE to receive ICMP error report packets. Ignored when
  /// AcceptPromiscuous or AcceptAnyProtocol is TRUE.
  ///
  BOOLEAN             AcceptIcmpErrors;
  ///
  /// Set to TRUE to receive broadcast IPv4 packets. Ignored when
  /// AcceptPromiscuous is TRUE.
  /// Set to FALSE to stop receiving broadcast IPv4 packets.
  ///
  BOOLEAN             AcceptBroadcast;
  ///
  /// Set to TRUE to receive all IPv4 packets that are sent to any
  /// hardware address or any protocol address.
  /// Set to FALSE to stop receiving all promiscuous IPv4 packets
  ///
  BOOLEAN             AcceptPromiscuous;
  ///
  /// Set to TRUE to use the default IPv4 address and default routing table.
  ///
  BOOLEAN             UseDefaultAddress;
  ///
  /// The station IPv4 address that will be assigned to this EFI IPv4Protocol instance.
  ///
  EFI_IPv4_ADDRESS    StationAddress;
  ///
  /// The subnet address mask that is associated with the station address.
  ///
  EFI_IPv4_ADDRESS    SubnetMask;
  ///
  /// TypeOfService field in transmitted IPv4 packets.
  ///
  UINT8               TypeOfService;
  ///
  /// TimeToLive field in transmitted IPv4 packets.
  ///
  UINT8               TimeToLive;
  ///
  /// State of the DoNotFragment bit in transmitted IPv4 packets.
  ///
  BOOLEAN             DoNotFragment;
  ///
  /// Set to TRUE to send and receive unformatted packets. The other
  /// IPv4 receive filters are still applied. Fragmentation is disabled for RawData mode.
  ///
  BOOLEAN             RawData;
  ///
  /// The timer timeout value (number of microseconds) for the
  /// receive timeout event to be associated with each assembled
  /// packet. Zero means do not drop assembled packets.
  ///
  UINT32              ReceiveTimeout;
  ///
  /// The timer timeout value (number of microseconds) for the
  /// transmit timeout event to be associated with each outgoing
  /// packet. Zero means do not drop outgoing packets.
  ///
  UINT32              TransmitTimeout;
} EFI_IP4_CONFIG_DATA;

是跟IP4收发数据相关的参数,这些参数是通过EFI_IP4_PROTOCOLConfigure成员配置的。

Ip4AccpetFrame

Ip4AccpetFrame()是和Ip4ReceiveFrame()一起使用的,其调用流程如下:

Ip4DriverBindingStart
Ip4ReceiveFrame
Ip4Config2SetManualAddress
Ip4Config2SetDefaultAddr
Ip4Config2OnPolicyChanged
Ip4AccpetFrame

Ip4ReceiveFrame()Ip4AccpetFrame()在代码中使用的情况有以下的几个:

// 位于Ip4Config2OnPolicyChanged()/Ip4Config2SetDefaultAddr()/Ip4Config2SetManualAddress():
Ip4ReceiveFrame (IpIf, NULL, Ip4AccpetFrame, IpSb);

//
// 位于Ip4DriverBindingStart()(Ip4DriverBindingStop()中的不关注):
// Ready to go: start the receiving and timer.
// Ip4Config2SetPolicy maybe call Ip4ReceiveFrame() to set the default interface's RecvRequest first after
// Ip4Config2 instance is initialized. So, EFI_ALREADY_STARTED is the allowed return status.
//
Status = Ip4ReceiveFrame (IpSb->DefaultInterface, NULL, Ip4AccpetFrame, IpSb);

首先需要介绍Ip4ReceiveFrame(),该函数位于NetworkPkg\Ip4Dxe\Ip4If.c:

EFI_STATUS
Ip4ReceiveFrame (
  IN  IP4_INTERFACE       *Interface,
  IN  IP4_PROTOCOL        *IpInstance       OPTIONAL,
  IN  IP4_FRAME_CALLBACK  CallBack,
  IN  VOID                *Context
  )
{
  Token = Ip4CreateLinkRxToken (Interface, IpInstance, CallBack, Context);

  Interface->RecvRequest = Token;
  Status                 = Interface->Mnp->Receive (Interface->Mnp, &Token->MnpToken);
}

这里的CallBack就是Ip4AccpetFrame(),对应到IP4_LINK_RX_TOKEN这个Token的CallBack成员。Token来自函数Ip4CreateLinkRxToken()

IP4_LINK_RX_TOKEN *
Ip4CreateLinkRxToken (
  IN IP4_INTERFACE       *Interface,
  IN IP4_PROTOCOL        *IpInstance,
  IN IP4_FRAME_CALLBACK  CallBack,
  IN VOID                *Context
  )
{
  Token = AllocatePool (sizeof (IP4_LINK_RX_TOKEN));

  Token->Signature  = IP4_FRAME_RX_SIGNATURE;
  Token->Interface  = Interface;
  Token->IpInstance = IpInstance;
  Token->CallBack   = CallBack;
  Token->Context    = Context;

  MnpToken         = &Token->MnpToken;
  MnpToken->Status = EFI_NOT_READY;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  Ip4OnFrameReceived,
                  Token,
                  &MnpToken->Event
                  );

  MnpToken->Packet.RxData = NULL;
}

所以当MNP接收到数据之后会执行Ip4OnFrameReceived()

VOID
EFIAPI
Ip4OnFrameReceived (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Request Ip4OnFrameReceivedDpc as a DPC at TPL_CALLBACK
  //
  QueueDpc (TPL_CALLBACK, Ip4OnFrameReceivedDpc, Context);
}

它注册了DPC函数Ip4OnFrameReceivedDpc()

VOID
EFIAPI
Ip4OnFrameReceivedDpc (
  IN VOID  *Context
  )
{
  EFI_MANAGED_NETWORK_COMPLETION_TOKEN  *MnpToken;
  EFI_MANAGED_NETWORK_RECEIVE_DATA      *MnpRxData;
  IP4_LINK_RX_TOKEN                     *Token;
  NET_FRAGMENT                          Netfrag;
  NET_BUF                               *Packet;
  UINT32                                Flag;

  Token = (IP4_LINK_RX_TOKEN *)Context;
  NET_CHECK_SIGNATURE (Token, IP4_FRAME_RX_SIGNATURE);

  //
  // First clear the interface's receive request in case the
  // caller wants to call Ip4ReceiveFrame in the callback.
  //
  Token->Interface->RecvRequest = NULL;

  MnpToken  = &Token->MnpToken;
  MnpRxData = MnpToken->Packet.RxData;

  if (EFI_ERROR (MnpToken->Status) || (MnpRxData == NULL)) {
    Token->CallBack (Token->IpInstance, NULL, MnpToken->Status, 0, Token->Context);
    Ip4FreeFrameRxToken (Token);

    return;
  }

  //
  // Wrap the frame in a net buffer then deliver it to IP input.
  // IP will reassemble the packet, and deliver it to upper layer
  //
  Netfrag.Len  = MnpRxData->DataLength;
  Netfrag.Bulk = MnpRxData->PacketData;

  Packet = NetbufFromExt (&Netfrag, 1, 0, IP4_MAX_HEADLEN, Ip4RecycleFrame, Token);

  if (Packet == NULL) {
    gBS->SignalEvent (MnpRxData->RecycleEvent);

    Token->CallBack (Token->IpInstance, NULL, EFI_OUT_OF_RESOURCES, 0, Token->Context);
    Ip4FreeFrameRxToken (Token);

    return;
  }

  Flag  = (MnpRxData->BroadcastFlag ? IP4_LINK_BROADCAST : 0);
  Flag |= (MnpRxData->MulticastFlag ? IP4_LINK_MULTICAST : 0);
  Flag |= (MnpRxData->PromiscuousFlag ? IP4_LINK_PROMISC : 0);

  Token->CallBack (Token->IpInstance, Packet, EFI_SUCCESS, Flag, Token->Context);
}

最终调用Token->CallBack(),其实就是调用Ip4AccpetFrame(),该函数的实现在NetworkPkg\Ip4Dxe\Ip4Input.c,代码大致如下:

VOID
Ip4AccpetFrame (
  IN IP4_PROTOCOL  *Ip4Instance,
  IN NET_BUF       *Packet,
  IN EFI_STATUS    IoStatus,
  IN UINT32        Flag,
  IN VOID          *Context
  )
{
  // 参数判断
  if (EFI_ERROR (IoStatus) || (IpSb->State == IP4_SERVICE_DESTROY)) {
    goto DROP;
  }
  if (!Ip4IsValidPacketLength (Packet)) {
    goto RESTART;
  }

  Head = (IP4_HEAD *)NetbufGetByte (Packet, 0, NULL);
  ASSERT (Head != NULL);
  OptionLen = (Head->HeadLen << 2) - IP4_MIN_HEADLEN;
  if (OptionLen > 0) {
    Option = (UINT8 *)(Head + 1);
  }

  //
  // Validate packet format and reassemble packet if it is necessary.
  //
  Status = Ip4PreProcessPacket (
             IpSb,
             &Packet,
             Head,
             Option,
             OptionLen,
             Flag
             );

  if (EFI_ERROR (Status)) {
    goto RESTART;
  }

  //
  // After trim off, the packet is a esp/ah/udp/tcp/icmp6 net buffer,
  // and no need consider any other ahead ext headers.
  //
  Status = Ip4IpSecProcessPacket (
             IpSb,
             &Head,
             &Packet,
             &Option,
             &OptionLen,
             EfiIPsecInBound,
             NULL
             );

  if (EFI_ERROR (Status)) {
    goto RESTART;
  }

  //
  // If the packet is protected by tunnel mode, parse the inner Ip Packet.
  //
  ZeroMem (&ZeroHead, sizeof (IP4_HEAD));
  if (0 == CompareMem (Head, &ZeroHead, sizeof (IP4_HEAD))) {
    // Packet may have been changed. Head, HeadLen, TotalLen, and
    // info must be reloaded before use. The ownership of the packet
    // is transferred to the packet process logic.
    //
    if (!Ip4IsValidPacketLength (Packet)) {
      goto RESTART;
    }

    Head = (IP4_HEAD *)NetbufGetByte (Packet, 0, NULL);
    ASSERT (Head != NULL);
    Status = Ip4PreProcessPacket (
               IpSb,
               &Packet,
               Head,
               Option,
               OptionLen,
               Flag
               );
    if (EFI_ERROR (Status)) {
      goto RESTART;
    }
  }

  ASSERT (Packet != NULL);
  Head                               = Packet->Ip.Ip4;
  IP4_GET_CLIP_INFO (Packet)->Status = EFI_SUCCESS;

  switch (Head->Protocol) {
    case EFI_IP_PROTO_ICMP:
      Ip4IcmpHandle (IpSb, Head, Packet);
      break;

    case IP4_PROTO_IGMP:
      Ip4IgmpHandle (IpSb, Head, Packet);
      break;

    default:
      Ip4Demultiplex (IpSb, Head, Packet, Option, OptionLen);
  }

  Packet = NULL;

  //
  // Dispatch the DPCs queued by the NotifyFunction of the rx token's events
  // which are signaled with received data.
  //
  DispatchDpc ();

RESTART:
  Ip4ReceiveFrame (IpSb->DefaultInterface, NULL, Ip4AccpetFrame, IpSb);

DROP:
  if (Packet != NULL) {
    NetbufFree (Packet);
  }

  return;
}

入参Packet是需要处理的参数,而数据处理主要经过Ip4PreProcessPacket()Ip4IpSecProcessPacket()处理之后,进入真正的处理阶段:

  switch (Head->Protocol) {
    case EFI_IP_PROTO_ICMP:
      Ip4IcmpHandle (IpSb, Head, Packet);
      break;

    case IP4_PROTO_IGMP:
      Ip4IgmpHandle (IpSb, Head, Packet);
      break;

    default:
      Ip4Demultiplex (IpSb, Head, Packet, Option, OptionLen);
  }

它会处理:

  • ICMP,Internet Control Message Protocol,互联网控制消息协议。
  • IGMP,Internet Group Management Ptotocol,互联网组管理协议。
  • 其它处理。

注意代码中的RESTART,又开始执行Ip4ReceiveFrame,所以这是一个循环的过程,表示IP4开始处理接收到的数据。

IP4事件

IP4的几个主要事件都在Ip4CreateService()中创建:

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL | EVT_TIMER,
                  TPL_CALLBACK,
                  Ip4TimerTicking,
                  IpSb,
                  &IpSb->Timer
                  );
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL | EVT_TIMER,
                  TPL_CALLBACK,
                  Ip4TimerReconfigChecking,
                  IpSb,
                  &IpSb->ReconfigCheckTimer
                  );
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  Ip4AutoReconfigCallBack,
                  IpSb,
                  &IpSb->ReconfigEvent
                  );

Timer

这是一个定时事件,从注释中可以看到它是一个心跳事件,对应的回调函数是Ip4TimerTicking(),它处理两个部分的内容,可以从其实现看出来(位于NetworkPkg\Ip4Dxe\Ip4Impl.c):

/**
  This heart beat timer of IP4 service instance times out all of its IP4 children's
  received-but-not-delivered and transmitted-but-not-recycle packets, and provides
  time input for its IGMP protocol.

  @param[in]  Event                  The IP4 service instance's heart beat timer.
  @param[in]  Context                The IP4 service instance.

**/
VOID
EFIAPI
Ip4TimerTicking (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  Ip4PacketTimerTicking (IpSb);
  Ip4IgmpTicking (IpSb);
}

从注释中已经说明了这个函数的作用。

Ip4TimerReconfigChecking

这是一个定时事件,该事件用于处理网络连接是否正常,对应回调函数是Ip4TimerReconfigChecking()

/**
  This dedicated timer is used to poll underlying network media status. In case
  of cable swap or wireless network switch, a new round auto configuration will
  be initiated. The timer will signal the IP4 to run DHCP configuration again.
  IP4 driver will free old IP address related resource, such as route table and
  Interface, then initiate a DHCP process to acquire new IP, eventually create
  route table for new IP address.

  @param[in]  Event                  The IP4 service instance's heart beat timer.
  @param[in]  Context                The IP4 service instance.

**/
VOID
EFIAPI
Ip4TimerReconfigChecking (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  OldMediaPresent = IpSb->MediaPresent;

  //
  // Get fresh mode data from MNP, since underlying media status may change.
  // Here, it needs to mention that the MediaPresent can also be checked even if
  // EFI_NOT_STARTED returned while this MNP child driver instance isn't configured.
  //
  Status = IpSb->Mnp->GetModeData (IpSb->Mnp, NULL, &SnpModeData);

  IpSb->MediaPresent = SnpModeData.MediaPresent;
  //
  // Media transimit Unpresent to Present means new link movement is detected.
  //
  if (!OldMediaPresent && IpSb->MediaPresent && (IpSb->Ip4Config2Instance.Policy == Ip4Config2PolicyDhcp)) {
    //
    // Signal the IP4 to run the dhcp configuration again. IP4 driver will free
    // old IP address related resource, such as route table and Interface, then
    // initiate a DHCP round to acquire new IP, eventually
    // create route table for new IP address.
    //
    if (IpSb->ReconfigEvent != NULL) {
      Status = gBS->SignalEvent (IpSb->ReconfigEvent);
      DispatchDpc ();
    }
  }
}

重点在于根据底层的MediaPresent来确定是否需要重新DHCP(前提是IP配置策略就是DHCP,静态则不需要关注),而重新DHCP还会使用到事件ReconfigEvent,它对应到回调函数Ip4AutoReconfigCallBack(),这才是真正做DHCP的函数,最终调用的是Ip4StartAutoConfig(),这部分在IP配置有详细介绍。

EFI_IP4_PROTOCOL

该Protocol的结构体如下:

///
/// The EFI IPv4 Protocol implements a simple packet-oriented interface that can be
/// used by drivers, daemons, and applications to transmit and receive network packets.
///
struct _EFI_IP4_PROTOCOL {
  EFI_IP4_GET_MODE_DATA    GetModeData;
  EFI_IP4_CONFIGURE        Configure;
  EFI_IP4_GROUPS           Groups;
  EFI_IP4_ROUTES           Routes;
  EFI_IP4_TRANSMIT         Transmit;
  EFI_IP4_RECEIVE          Receive;
  EFI_IP4_CANCEL           Cancel;
  EFI_IP4_POLL             Poll;
};

其实现位于NetworkPkg\Ip4Dxe\Ip4Impl.c:

EFI_IP4_PROTOCOL mEfiIp4ProtocolTemplete = {
  EfiIp4GetModeData,
  EfiIp4Configure,
  EfiIp4Groups,
  EfiIp4Routes,
  EfiIp4Transmit,
  EfiIp4Receive,
  EfiIp4Cancel,
  EfiIp4Poll
};

后面会介绍这些函数的实现。

Ip4.GetModeData

对应的实现是EfiIp4GetModeData(),它不仅获取IP4的数据,还会获取MNPSNP的数据,且都是可选的:

EFI_STATUS
EFIAPI
EfiIp4GetModeData (
  IN  CONST EFI_IP4_PROTOCOL                 *This,
  OUT       EFI_IP4_MODE_DATA                *Ip4ModeData     OPTIONAL,
  OUT       EFI_MANAGED_NETWORK_CONFIG_DATA  *MnpConfigData   OPTIONAL,
  OUT       EFI_SIMPLE_NETWORK_MODE          *SnpModeData     OPTIONAL
  )
{
  // 获取IP4数据,它们主要来自IP4_PROTOCOL
  if (Ip4ModeData != NULL) {
    //
    // IsStarted is "whether the EfiIp4Configure has been called".
    // IsConfigured is "whether the station address has been configured"
    //
    Ip4ModeData->IsStarted = (BOOLEAN)(IpInstance->State == IP4_STATE_CONFIGED);
    CopyMem (&Ip4ModeData->ConfigData, &IpInstance->ConfigData, sizeof (Ip4ModeData->ConfigData));
    Ip4ModeData->IsConfigured = FALSE;

    Ip4ModeData->GroupCount = IpInstance->GroupCount;
    Ip4ModeData->GroupTable = (EFI_IPv4_ADDRESS *)IpInstance->Groups;

    Ip4ModeData->IcmpTypeCount = 23;
    Ip4ModeData->IcmpTypeList  = mIp4SupportedIcmp;

    Ip4ModeData->RouteTable = NULL;
    Ip4ModeData->RouteCount = 0;

    Ip4ModeData->MaxPacketSize = IpSb->MaxPacketSize;

    //
    // return the current station address for this IP child. So,
    // the user can get the default address through this. Some
    // application wants to know it station address even it is
    // using the default one, such as a ftp server.
    //
    if (Ip4ModeData->IsStarted) {
      Config = &Ip4ModeData->ConfigData;

      Ip = HTONL (IpInstance->Interface->Ip);
      CopyMem (&Config->StationAddress, &Ip, sizeof (EFI_IPv4_ADDRESS));

      Ip = HTONL (IpInstance->Interface->SubnetMask);
      CopyMem (&Config->SubnetMask, &Ip, sizeof (EFI_IPv4_ADDRESS));

      Ip4ModeData->IsConfigured = IpInstance->Interface->Configured;

      //
      // Build a EFI route table for user from the internal route table.
      //
      Status = Ip4BuildEfiRouteTable (IpInstance);

      Ip4ModeData->RouteTable = IpInstance->EfiRouteTable;
      Ip4ModeData->RouteCount = IpInstance->EfiRouteCount;
    }
  }

  // 获取MNP和SNP的数据
  //
  // Get fresh mode data from MNP, since underlying media status may change
  //
  Status = IpSb->Mnp->GetModeData (IpSb->Mnp, MnpConfigData, SnpModeData);
}

Ip4.Configure

对应的实现是EfiIp4Configure(),其代码实现:

EFI_STATUS
EFIAPI
EfiIp4Configure (
  IN EFI_IP4_PROTOCOL     *This,
  IN EFI_IP4_CONFIG_DATA  *IpConfigData       OPTIONAL
  )
{
  //
  // Validate the configuration first.
  //
  if (IpConfigData != NULL) {
    // 获取IP地址和掩码
    CopyMem (&IpAddress, &IpConfigData->StationAddress, sizeof (IP4_ADDR));
    CopyMem (&SubnetMask, &IpConfigData->SubnetMask, sizeof (IP4_ADDR));

    IpAddress  = NTOHL (IpAddress);
    SubnetMask = NTOHL (SubnetMask);

    //
    // Check whether the station address is a valid unicast address
    //
    if (!IpConfigData->UseDefaultAddress) {
      // 不适用默认的IP,表示需要用新IP,则需要验证新IP是否有效
      AddrOk = Ip4StationAddressValid (IpAddress, SubnetMask);

      if (!AddrOk) {
        Status = EFI_INVALID_PARAMETER;
        goto ON_EXIT;
      }
    }

    //
    // User can only update packet filters when already configured.
    // If it wants to change the station address, it must configure(NULL)
    // the instance first.
    //
    if (IpInstance->State == IP4_STATE_CONFIGED) {
      Current = &IpInstance->ConfigData;

      if (Current->UseDefaultAddress != IpConfigData->UseDefaultAddress) {
        Status = EFI_ALREADY_STARTED;
        goto ON_EXIT;
      }

      if (!Current->UseDefaultAddress &&
          (!EFI_IP4_EQUAL (&Current->StationAddress, &IpConfigData->StationAddress) ||
           !EFI_IP4_EQUAL (&Current->SubnetMask, &IpConfigData->SubnetMask)))
      {
        Status = EFI_ALREADY_STARTED;
        goto ON_EXIT;
      }

      if (Current->UseDefaultAddress && IP4_NO_MAPPING (IpInstance)) {
        Status = EFI_NO_MAPPING;
        goto ON_EXIT;
      }
    }
  }

  //
  // Configure the instance or clean it up.
  //
  if (IpConfigData != NULL) {
    Status = Ip4ConfigProtocol (IpInstance, IpConfigData);
  } else {
    Status = Ip4CleanProtocol (IpInstance);

    //
    // Consider the following valid sequence: Mnp is unloaded-->Ip Stopped-->Udp Stopped,
    // Configure (ThisIp, NULL). If the state is changed to UNCONFIGED,
    // the unload fails miserably.
    //
    if (IpInstance->State == IP4_STATE_CONFIGED) {
      IpInstance->State = IP4_STATE_UNCONFIGED;
    }
  }

  //
  // Update the MNP's configure data. Ip4ServiceConfigMnp will check
  // whether it is necessary to reconfigure the MNP.
  //
  Ip4ServiceConfigMnp (IpInstance->Service, FALSE);
}

IpConfigData可以是NULL,表示清除IP配置。最终调用的配置函数是Ip4ConfigProtocol()

EFI_STATUS
Ip4ConfigProtocol (
  IN OUT IP4_PROTOCOL         *IpInstance,
  IN     EFI_IP4_CONFIG_DATA  *Config
  )
{
  // 如果已经配置过了,则只需要更新数据即可
  //
  // User is changing packet filters. It must be stopped
  // before the station address can be changed.
  //
  if (IpInstance->State == IP4_STATE_CONFIGED) {
    //
    // Cancel all the pending transmit/receive from upper layer
    //
    Status = Ip4Cancel (IpInstance, NULL);

    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }

    CopyMem (&IpInstance->ConfigData, Config, sizeof (IpInstance->ConfigData));
    return EFI_SUCCESS;
  }

  //
  // Configure a fresh IP4 protocol instance. Create a route table.
  // Each IP child has its own route table, which may point to the
  // default table if it is using default address.
  //
  Status                 = EFI_OUT_OF_RESOURCES;
  IpInstance->RouteTable = Ip4CreateRouteTable ();

  //
  // Set up the interface.
  //
  CopyMem (&Ip, &Config->StationAddress, sizeof (IP4_ADDR));
  CopyMem (&Netmask, &Config->SubnetMask, sizeof (IP4_ADDR));

  Ip      = NTOHL (Ip);
  Netmask = NTOHL (Netmask);

  if (!Config->UseDefaultAddress) {
    //
    // Find whether there is already an interface with the same
    // station address. All the instances with the same station
    // address shares one interface.
    //
    IpIf = Ip4FindStationAddress (IpSb, Ip, Netmask);
    if (IpIf != NULL) {
      NET_GET_REF (IpIf);
    } else {
      IpIf = Ip4CreateInterface (IpSb->Mnp, IpSb->Controller, IpSb->Image);
      Status = Ip4SetAddress (IpIf, Ip, Netmask);
      InsertTailList (&IpSb->Interfaces, &IpIf->Link);
    }
    //
    // Add a route to this connected network in the instance route table.
    //
    Ip4AddRoute (
      IpInstance->RouteTable,
      Ip & Netmask,
      Netmask,
      IP4_ALLZERO_ADDRESS
      );
  } else {
    //
    // Use the default address. Check the state.
    //
    if (IpSb->State == IP4_SERVICE_UNSTARTED) {
      //
      // Trigger the EFI_IP4_CONFIG2_PROTOCOL to retrieve the
      // default IPv4 address if it is not available yet.
      //
      Policy = IpSb->Ip4Config2Instance.Policy;
      if (Policy != Ip4Config2PolicyDhcp) {
        Ip4Config2 = &IpSb->Ip4Config2Instance.Ip4Config2;
        Policy     = Ip4Config2PolicyDhcp;
        Status     = Ip4Config2->SetData (
                                   Ip4Config2,
                                   Ip4Config2DataTypePolicy,
                                   sizeof (EFI_IP4_CONFIG2_POLICY),
                                   &Policy
                                   );
      }
    }

    IpIf = IpSb->DefaultInterface;
    NET_GET_REF (IpSb->DefaultInterface);

    //
    // If default address is used, so is the default route table.
    // Any route set by the instance has the precedence over the
    // routes in the default route table. Link the default table
    // after the instance's table. Routing will search the local
    // table first.
    //
    NET_GET_REF (IpSb->DefaultRouteTable);
    IpInstance->RouteTable->Next = IpSb->DefaultRouteTable;
  }

  IpInstance->Interface = IpIf;
  if (IpIf->Arp != NULL) {
    Arp    = NULL;
    Status = gBS->OpenProtocol (
                    IpIf->ArpHandle,
                    &gEfiArpProtocolGuid,
                    (VOID **)&Arp,
                    gIp4DriverBinding.DriverBindingHandle,
                    IpInstance->Handle,
                    EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                    );
  }

  InsertTailList (&IpIf->IpInstances, &IpInstance->AddrLink);

  CopyMem (&IpInstance->ConfigData, Config, sizeof (IpInstance->ConfigData));
  IpInstance->State = IP4_STATE_CONFIGED;
}

代码重点在于创建IP4_INTERFACE,并更新IP4_PROTOCOL的数据。

Ip4.Transmit

对应的实现是EfiIp4Transmit(),IP4数据传输比较复杂:

  1. 首先是IP报文的构造,根据配置中的RawData参数,会有不同的流程:
  //
  // Build the IP header, need to fill in the Tos, TotalLen, Id,
  // fragment, Ttl, protocol, Src, and Dst.
  //
  TxData = Token->Packet.TxData;
  if (Config->RawData) {
    //
    // When RawData is TRUE, first buffer in FragmentTable points to a raw
    // IPv4 fragment including IPv4 header and options.
    //
  } else {
  }
  1. 之后数据经过进一步地包装:
  //
  // OK, it survives all the validation check. Wrap the token in
  // a IP4_TXTOKEN_WRAP and the data in a netbuf
  //
  1. 最后执行输出操作:
  Status = Ip4Output (
             IpSb,
             IpInstance,
             Wrap->Packet,
             &Head,
             OptionsBuffer,
             OptionsLength,
             GateWay,
             Ip4OnPacketSent,
             Wrap
             );

当然输出操作也没有那么简单,如果有安全协议(目前没有)则需要经过数据的安全处理,且因为路由的存在,需要找到下一跳节点的地址,最终通过MNP将数据发送出去。

Ip4.Receive

对应的实现是EfiIp4Receive(),跟其他UEFI下的网络协议一样,Receive通常并不意味着数据的接收,而仅仅是注册数据的处理函数:

EFI_STATUS
EFIAPI
EfiIp4Receive (
  IN EFI_IP4_PROTOCOL          *This,
  IN EFI_IP4_COMPLETION_TOKEN  *Token
  )
{
  //
  // Check whether the toke is already on the receive queue.
  //
  Status = NetMapIterate (&IpInstance->RxTokens, Ip4TokenExist, Token);

  //
  // Queue the token then check whether there is pending received packet.
  //
  Status = NetMapInsertTail (&IpInstance->RxTokens, Token, NULL);

  Status = Ip4InstanceDeliverPacket (IpInstance);

  //
  // Dispatch the DPC queued by the NotifyFunction of this instane's receive
  // event.
  //
  DispatchDpc ();
}

Ip4.Poll

对应的实现是EfiIp4Poll(),该函数仅仅是调用MNPPoll成员函数:

EFI_STATUS
EFIAPI
EfiIp4Poll (
  IN EFI_IP4_PROTOCOL  *This
  )
{
  Mnp = IpInstance->Service->Mnp;

  //
  // Don't lock the Poll function to enable the deliver of
  // the packet polled up.
  //
  return Mnp->Poll (Mnp);
}

MNPPoll成员函数会接受网络数据,并执行上层网络协议注册的回调接口来处理数据,这样IP4的注册接口也会被调用。

IP4代码示例

IP4的一个很好的代码示例就是ping程序,其执行显示:

在这里插入图片描述

对应的代码文件为ShellPkg\Library\UefiShellNetwork1CommandsLib\Ping.c,它是ShellPKg中的一部分,本节主要分析这部分的内容。首先是入口函数:

  //
  // Enter into ping process.
  //
  ShellStatus = ShellPing (
                  (UINT32)SendNumber,
                  (UINT32)BufferSize,
                  &SrcAddress,
                  &DstAddress,
                  IpChoice
                  );

关于参数的说明:

  • SendNumber:表示ping的次数,通过-n参数指定,如果不指定,则默认是10次。
  • BufferSize:表示报文的大小,通过-l参数指定,如果不指定,则默认是16字节。
  • SrcAddress:源IP地址。
  • DstAddress:目标IP地址。
  • IpChoice:选择IPv4还是IPv6,这里我们只关注IPv4。

该函数的执行流程:

PingCreateIpInstance
Ping6ReceiveEchoReply
创建定时事件Timer
PingInitRttTimer
PingSendEchoRequest
while循环等待事件结束
打印ping的结果

这里有几个重点函数需要说明。

  • 首先是Ping6ReceiveEchoReply(),虽然这里有一个6,但是并不是真的IPv6,只是名字而已,不是很重要。该函数的重点是一个Token的初始化:
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ping6OnEchoReplyReceived,
                  Private,
                  &Private->RxToken.Event
                  );
  Private->RxToken.Status = EFI_NOT_READY;
  Status = Private->ProtocolPointers.Receive (Private->IpProtocol, &Private->RxToken);

这里使用了一个PING_IPX_PROTOCOL,其结构体:

///
/// A set of pointers to either IPv6 or IPv4 functions.
/// Unknown which one to the ping command.
///
typedef struct {
  PING_IPX_TRANSMIT    Transmit;
  PING_IPX_RECEIVE     Receive;
  PING_IPX_CANCEL      Cancel;
  PING_IPX_POLL        Poll;
} PING_IPX_PROTOCOL;

它实际上只是对EFI_IP4_PROTOCOL或者EFI_IP6_PROTOCOL的包装而已,其初始化在前面提到的PingCreateIpInstance()中,以IPv4版本为例:

    Private->ProtocolPointers.Transmit = (PING_IPX_TRANSMIT)((EFI_IP4_PROTOCOL *)Private->IpProtocol)->Transmit;
    Private->ProtocolPointers.Receive  = (PING_IPX_RECEIVE)((EFI_IP4_PROTOCOL *)Private->IpProtocol)->Receive;
    Private->ProtocolPointers.Cancel   = (PING_IPX_CANCEL)((EFI_IP4_PROTOCOL *)Private->IpProtocol)->Cancel;
    Private->ProtocolPointers.Poll     = (PING_IPX_POLL)((EFI_IP4_PROTOCOL *)Private->IpProtocol)->Poll;

所以最终实际上调用的是EFI_IP4_PROTOCOLReceive()函数。

所以本函数最终的目的就是创建Token来处理IP层的数据,而处理函数就是这里的回调函数Ping6OnEchoReplyReceived()

  • 然后介绍回调函数Ping6OnEchoReplyReceived(),它的实现就是处理接收到的报文:
    Reply   = ((EFI_IP4_RECEIVE_DATA *)Private->RxToken.Packet.RxData)->FragmentTable[0].FragmentBuffer;
    PayLoad = ((EFI_IP4_RECEIVE_DATA *)Private->RxToken.Packet.RxData)->DataLength;
    if (!IP4_IS_MULTICAST (EFI_IP4 (*(EFI_IPv4_ADDRESS *)Private->DstAddress)) &&
        !EFI_IP4_EQUAL (&((EFI_IP4_RECEIVE_DATA *)Private->RxToken.Packet.RxData)->Header->SourceAddress, (EFI_IPv4_ADDRESS *)&Private->DstAddress))
    {
      goto ON_EXIT;
    }

    if ((Reply->Type != ICMP_V4_ECHO_REPLY) || (Reply->Code != 0)) {
      goto ON_EXIT;
    }
  }

  if (PayLoad != Private->BufferSize) {
    goto ON_EXIT;
  }

  //
  // Check whether the reply matches the sent request before.
  //
  Status = Ping6MatchEchoReply (Private, Reply);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // Display statistics on this icmp6 echo reply packet.
  //
  Rtt = CalculateTick (Private, Reply->TimeStamp, ReadTime (Private));

  Private->RttSum += Rtt;
  Private->RttMin  = Private->RttMin > Rtt ? Rtt : Private->RttMin;
  Private->RttMax  = Private->RttMax < Rtt ? Rtt : Private->RttMax;

  ShellPrintHiiEx (
    -1,
    -1,
    NULL,
    STRING_TOKEN (STR_PING_REPLY_INFO),
    gShellNetwork1HiiHandle,
    PayLoad,
    mDstString,
    Reply->SequenceNum,
    Private->IpChoice == PING_IP_CHOICE_IP6 ? ((EFI_IP6_RECEIVE_DATA *)Private->RxToken.Packet.RxData)->Header->HopLimit : 0,
    Rtt,
    Rtt + Private->TimerPeriod
    );

这个也没有什么好多介绍的,就是普通的协议层处理。最终会打印每次ping的结果,而当次数到了命令行参数的要求之后,就会退出,对应代码:

  if (Private->RxCount < Private->SendNum) {
    //
    // Continue to receive icmp echo reply packets.
    //
    Private->RxToken.Status = EFI_ABORTED;

    Status = Private->ProtocolPointers.Receive (Private->IpProtocol, &Private->RxToken);

    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_RECEIVE), gShellNetwork1HiiHandle, Status);
      Private->Status = EFI_ABORTED;
    }
  } else {
    //
    // All reply have already been received from the dest host.
    //
    Private->Status = EFI_SUCCESS;
  }

到这里只是一个准备工作,因为实际上也还没有发送数据,所以这里只是准备接收而已。

  • 之后介绍PingInitRttTimer(),这里的RTT的全称是Round Trip Time,它是表示网络延时的一个性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。代码通过一个定时器来计时:
  Private->RttTimerTick = 0;
  Status                = gBS->CreateEvent (
                                 EVT_TIMER | EVT_NOTIFY_SIGNAL,
                                 TPL_NOTIFY,
                                 RttTimerTickRoutine,
                                 &Private->RttTimerTick,
                                 &Private->RttTimer
                                 );
  Status = gBS->SetTimer (
                  Private->RttTimer,
                  TimerPeriodic,
                  TICKS_PER_MS
                  );

它的定时间隔是1ms,所以在前面的运行过程中可以看到得到的结果都是以毫秒计的。

  • 再后面就是发送IP报文,使用的是函数PingSendEchoRequest(),其中最重要的代码就是发送数据:
  Status = Private->ProtocolPointers.Transmit (Private->IpProtocol, TxInfo->Token);

发送的数据通过PingGenerateToken()函数创建,它也是包装在一个Token中的,其内容:

  //
  // Assembly echo request packet.
  //
  Request->Type        = (UINT8)(Private->IpChoice == PING_IP_CHOICE_IP6 ? ICMP_V6_ECHO_REQUEST : ICMP_V4_ECHO_REQUEST);
  Request->Code        = 0;
  Request->SequenceNum = SequenceNum;
  Request->Identifier  = 0;
  Request->Checksum    = 0;

  ((EFI_IP4_TRANSMIT_DATA *)TxData)->FragmentTable[0].FragmentBuffer = (VOID *)Request;
  ((EFI_IP4_TRANSMIT_DATA *)TxData)->FragmentTable[0].FragmentLength = Private->BufferSize;

ping是通过ICMP报文来实现的,对应到这里的Request,其对应一个结构体:

typedef struct _ICMPX_ECHO_REQUEST_REPLY {
  UINT8     Type;
  UINT8     Code;
  UINT16    Checksum;
  UINT16    Identifier;
  UINT16    SequenceNum;
  UINT32    TimeStamp;
  UINT8     Data[1];
} ICMPX_ECHO_REQUEST_REPLY;

ICMP中的对应。

  • 再后面是一个等待响应或者按键的过程:
  //
  // Control the ping6 process by two factors:
  // 1. Hot key
  // 2. Private->Status
  //   2.1. success means all icmp6 echo request packets get reply packets.
  //   2.2. timeout means the last icmp6 echo reply request timeout to get reply.
  //   2.3. noready means ping6 process is on-the-go.
  //
  while (Private->Status == EFI_NOT_READY) {
    Status = Private->ProtocolPointers.Poll (Private->IpProtocol);
    if (ShellGetExecutionBreakFlag ()) {
      Private->Status = EFI_ABORTED;
      goto ON_STAT;
    }
  }

这里的EFI_NOT_READY来自Ping6ReceiveEchoReply()中的初始化:

Private->RxToken.Status = EFI_NOT_READY;

之后当ping的次数超过命令行参数时会设置成EFI_ABORTED

  if (Private->RxCount < Private->SendNum) {
    //
    // Continue to receive icmp echo reply packets.
    //
    Private->RxToken.Status = EFI_ABORTED;

这样就退出了循环。

  • 最后是打印ping的结果,就是前面图中的最后两句。
  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值