IP全称Internet Protocol,它属于网络层,对其下各种类型的数据链路层进行了包装,这样网络层可以跨越不同的数据链路,即使是在不同的数据链路上也能实现两端节点之间的数据包传输。
IP层的主要作用就是“实现终端节点之间的通信”:
IPv4首部的格式如下:
各个参数的说明如下:
字段 | 长度(bit) | 含义 |
---|---|---|
Version | 4 | 4:表示为IPv4; 6:表示为IPv6。 |
IHL | 4 | 首部长度,如果不带Option字段,则为20,最长为60,该值限制了记录路由选项。 以4字节为一个单位。 |
Type of Service | 8 | 服务类型。只有在有QoS差分服务要求时这个字段才起作用。 |
Total Length | 16 | 总长度,整个IP数据报的长度,包括首部和数据之和,单位为字节,最长65535。 总长度必须不超过最大传输单元MTU。 |
Identification | 16 | 标识,主机每发一个报文,加1,分片重组时会用到该字段。 |
Flags | 3 | 标志位: Bit 0:保留位,必须为0。 Bit 1:DF(Don’t Fragment),能否分片位,0表示可以分片,1表示不能分片。 Bit 2:MF(More Fragment),表示是否该报文为最后一片,0表示最后一片,1代表后面还有。 |
Fragment Offset | 13 | 片偏移:分片重组时会用到该字段。 表示较长的分组在分片后,某片在原分组中的相对位置。以8个字节为偏移单位。 |
Time to Live | 8 | 生存时间:可经过的最多路由数,即数据包在网络中可通过的路由器数的最大值。 |
Protocol | 8 | 协议:下一层协议。 指出此数据包携带的数据使用何种协议,以便目的主机的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 Checksum | 16 | 首部检验和,只检验数据包的首部,不检验数据部分。 这里不采用CRC检验码,而采用简单的计算方法。 |
Source Address | 32 | 源IP地址。 |
Destination Address | 32 | 目的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地址用于在“连接到网络中的所有主机中识别出进行通信的目标地址”,在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通信分为三类:
路由是指将分组数据发送到最终目标地址的功能。
发送数据到最终目标地址是通过“跳”(Hop)的方式实现的:
“跳”指网络中的一个区间,IP包正是在网络中一个个“跳”间被转发的,因此IP路由也叫做多跳路由。
在转发IP数据包时只指定下一个路由器或主机,而不是将最终目标地址为止的所有通路全都指定出来。因为每一跳在转发IP数据包时会指定下一跳的操作,直至包到达最终的目标地址。
发送数据包时所使用的地址是网络层的地址,即IP地址。然而仅仅有IP地址还不足以实现数据包发送到对端目的地址,在数据发送过程中还需要类似于“指明路由器或主机”的信息,以便真正发往目标地址。
为了将数据包发给目标主机,所有主机(包括路由器)都维护者一张路由表(路由控制表)。该表记录IP数据在下一步应该发给哪个路由器。IP包将根据这个路由表在各个数据链路上传输。
任何一台主机都有必要对IP包分片(IP Fragmentation)进行相应的处理。分片往往在网络上遇到比较大的报文无法一下子发送出去时才会进行处理。以太网的默认MTU(最大传输单元)1500个字节。
实际通信中光有IP协议是不够的,还需要与IP相关的技术才能实现。其中的一部分会在后续进一步说明,这里需要介绍的ICMP和IGMP。它们的实现就本节接收的驱动中。
ICMP和IGMP都是封装在IP报文中的。
ICMP全称是Internet Control Message Protocol,它的主要功能包括:确认IP包是否成功送达目标地址;通知在发送过程当中IP包被废弃的具体原因;改善网络设置;等等。其格式如下:
各个参数的说明如下:
字段 | 长度(字节) | 含义 |
---|---|---|
Type | 1 | 报文类型,用来标识报文,Type字段的取值和含义后面会进一步说明。 |
Code | 1 | 代码,提供报文类型的进一步信息,Code字段的取值和含义后面会进一步说明。 |
Checksum | 2 | 校验和,使用和IP相同的加法校验和算法,但是ICMP校验和仅覆盖ICMP报文。 |
Message Body | 可变 | 字段的长度和内容,取决于消息的类型和代码。 |
ICMP消息类型代码对应表如下:
类型Type | 代码Code | 描述 |
---|---|---|
0 | 0 | 回显应答(ping应答) |
3 | 0 | 网络不可达 |
3 | 1 | 主机不可达 |
3 | 2 | 协议不可达 |
3 | 3 | 端口不可达 |
3 | 4 | 需要进行分片但设置不分片比特 |
3 | 5 | 源站选路失败 |
3 | 6 | 目的网络不认识 |
3 | 7 | 目的主机不认识 |
3 | 8 | 源主机被隔离(作废不用) |
3 | 9 | 目的网络被强制禁止 |
3 | 10 | 目的主机被强制禁止 |
3 | 11 | 由于TOS,网络不可达 |
3 | 12 | 由于TOS,主机不可达 |
3 | 13 | 由于过滤,通信被强制禁止 |
3 | 14 | 主机越权 |
3 | 15 | 优先权中止生效 |
4 | 0 | 源端被关闭 |
5 | 0 | 对网络重定向 |
5 | 1 | 对主机重定向 |
5 | 2 | 对服务类型和网络重定向 |
5 | 3 | 对服务类型和主机重定向 |
8 | 0 | 请求回显(ping请求) |
9 | 0 | 路由器通告 |
10 | 0 | 路由器请求告 |
11 | 0 | 传输期间生存时间为0 |
11 | 1 | 在数据报组装期间生存时间为0 |
12 | 0 | 坏的IP首部 |
12 | 1 | 缺少必须的选项 |
13 | 0 | 时间戳请求(作废不用) |
14 | 0 | 时间戳应答(作废不用) |
15 | 0 | 信息请求(作废不用) |
16 | 0 | 信息应答(作废不用) |
17 | 0 | 地址掩码请求 |
18 | 0 | 地址掩码应答 |
IGMP全称是Internet Group Management Protocol,它的主要功能包括:在接受者主机和直接相连的组播路由器之间建立和维护组播成员的关系;在接受者主机和组播路由器之间交互IGMP报文实现组成员管理功能。
IGMP有多个版本,目前BIOS下维护的是IGMPv2,其格式如下:
各个参数的说明如下:
字段 | 长度(bit) | 描述 |
---|---|---|
Type | 8 | 报文类型,有以下几种类型: 0x11:Membership Query,IGMP查询消息。 0x12:Version 1 Membership Report,IGMPv1成员报告消息。 0x16:Version 2 Membership Report,IGMPv2成员报告消息。 0x17:Leave Group,离开消息。 |
Max Resp Time | 8 | 在发出响应报告前的以1/10秒为单位的最长时间,缺省值为10秒。 新的最大响应时间(以1/10秒为单位)字段允许查询用路由器为它的查询报文指定准确的查询间隔响应时间。 IGMP版本2主机在随机选择它们的响应时间值时以此作为上限。 这样在查询响应间隔时有助于控制响应的爆发。 |
Checksum | 16 | IGMP消息的校验和。 传送报文时,必须计算校验和并填入该字段中; 接收报文时,必须在处理报文之前检验校验和,以判断IGMP消息在传输过程中是否发生了错误。 |
Group Address | 32 | 组播组地址(如果是通用查询则为0.0.0.0)。 除了在通用查询时这一字段置为0.0.0.0外,这一字段和IGMP版本1中的这一字段意义相同。 |
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网络协议栈中的关系图:
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
);
}
Start函数的流程大致如下:
Ip4CreateService()
函数创建IP4服务,主要的实现就是初始化IP4_SERVICE
结构体。gEfiIp4ServiceBindingProtocolGuid
和gEfiIp4Config2ProtocolGuid
。前者跟其他网络驱动类似,就是一个服务接口;后者用于配置IP。IP4_CONFIG2_INSTANCE
,在Ip4CreateService()
中有默认的初始化,其流程大致是:EFI_IP4_CONFIG2_PROTOCOL
的SetData()
接口进行处理。Ip4CreateService()
中创建的。所以主要的操作还是在IP4_SERVICE
以及与它相关的结构体,它们的关系如下:
IP4的服务与MNP和ARP的服务对应,所以也可以有多个,每个IP4服务对应这样一个结构体。与ARP一样,由于不涉及到硬件,所以开始就是SERVICE结构体,而没有硬件相关的结构体。IP4_SERVICE
在Ip4DriverBindingStart()
中初始化,通过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()
。
这些参数涉及的都是IP的配置,后面还会进一步介绍。
NumChildren
、Children
: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
。MnpChildHandle
、Mnp
:IP4对应的EFI_MANAGED_NETWORK_PROTOCOL
及其Handle。MnpConfigData
:MNP配置数据,它的值也是固定的: 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;
SnpMode
:SNP模式。Timer
、ReconfigCheckTimer
、Ip4AutoReconfigCallBack
: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的服务数据和子项数据中间还有一个IP4_INTERFACE
。这是因为IP层的主要作用是终端节点之间的通信,而为了表示一个节点,必须要有IP地址、MAC等来描述。而IP4_INTERFACE
的作用主要就是用来描述节点。
IP4_INTERFACE
由Ip4CreateInterface()
创建:
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;
}
其调用流程包含两个部分:
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);
}
所以这只是一个默认的版本。
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;
};
它的成员包含几个部分:
Ip
、SubnetMask
、SubnetBrdcast
、NetBrdcast
:通用的IP地址。Configured
:执行Ip4SetAddres()
设置IP之后变成TRUE。Mnp
、Arp
:底层的网络接口。Mac
、BroadcastMac
、HwaddrLen
: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_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;
}
Link
,IfIndex
:一个网卡可能有多个网口,所以有IfIndex
,而Link
连接的是IP4_CONFIG2_INSTANCE
实例,它会根据IfIndex
而有多个,这些实例通过mIp4Config2InstanceList
连接。从目前的实现来看,应该只有1个,所以IfIndex
的值一直是0。
Ip4Config2
:EFI_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]
,其中包含了配置需要的大部分操作函数。
Dhcp4SbNotifyEvent
、Registration
、Dhcp4Handle
、Dhcp4
、Dhcp4Event
:DHCP4相关的参数,此时DHCP4还没有安装,所以这里是通过回调的方式来实现的。DhcpSuccess
:DHCP4初始化成功之后设置为TRUE
,来自函数Ip4Config2OnDhcp4Complete()
,它是Dhcp4Event
事件的回调函数。OtherInfoOnly
、FailedIaAddressCount
、DeclineAddress
、DeclineAddressCount
:当前未使用,它们有一个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配置涉及到几个重要的函数,这些函数在前面已经提到过,这里将进一步说明。
首先是Ip4CreateInterface()
,它创建了IP4_INTERFACE
结构体,这是配置IP的主体。其调用流程如下:
上述几个调用Ip4CreateInterface()
的代码可以分为三类,分别对应:
Configure
成员函数,后面会进一步介绍。DataItem[Ip4Config2DataTypePolicy]
和Ip4StartAutoConfig()
函数,前者实际上已经包含了后者,这可以从后者的调用情况看出来:这里的第一种调用,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需要进行配置之后才能够生效,比如Shell下使用ifconfig进行配置:
ifconfig -s eth0 static 192.168.3.128 255.255.255.0 192.168.3.1
它会执行ShellPkg\Library\UefiShellNetwork1CommandsLib\Ifconfig.c中的代码,其流程如下:
这里对应的IfCfg
是EFI_IP4_CONFIG2_PROTOCOL
的SetData
成员函数,其实现是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子项使用的数据结构体。注意与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_SERVICE
的Children
链表中。RxTokens
、TxTokens
、Received
、Delivered
:数据处理的结构体。Interface
:指向IP4_INTERFACE
实例。AddrLink
:具有相同IP配置的IP4_INTERFACE
链表。RouteTable
、EfiRouteTable
:路由表相关的参数。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_PROTOCOL
的Configure
成员配置的。
Ip4AccpetFrame()
是和Ip4ReceiveFrame()
一起使用的,其调用流程如下:
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);
}
它会处理:
注意代码中的RESTART
,又开始执行Ip4ReceiveFrame
,所以这是一个循环的过程,表示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
);
这是一个定时事件,从注释中可以看到它是一个心跳事件,对应的回调函数是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()
:
/**
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配置有详细介绍。
该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
};
后面会介绍这些函数的实现。
对应的实现是EfiIp4GetModeData()
,它不仅获取IP4的数据,还会获取MNP和SNP的数据,且都是可选的:
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);
}
对应的实现是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
的数据。
对应的实现是EfiIp4Transmit()
,IP4数据传输比较复杂:
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 {
}
//
// OK, it survives all the validation check. Wrap the token in
// a IP4_TXTOKEN_WRAP and the data in a netbuf
//
Status = Ip4Output (
IpSb,
IpInstance,
Wrap->Packet,
&Head,
OptionsBuffer,
OptionsLength,
GateWay,
Ip4OnPacketSent,
Wrap
);
当然输出操作也没有那么简单,如果有安全协议(目前没有)则需要经过数据的安全处理,且因为路由的存在,需要找到下一跳节点的地址,最终通过MNP将数据发送出去。
对应的实现是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 ();
}
对应的实现是EfiIp4Poll()
,该函数仅仅是调用MNP的Poll
成员函数:
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);
}
MNP的Poll
成员函数会接受网络数据,并执行上层网络协议注册的回调接口来处理数据,这样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。该函数的执行流程:
这里有几个重点函数需要说明。
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_PROTOCOL
的Receive()
函数。
所以本函数最终的目的就是创建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,所以在前面的运行过程中可以看到得到的结果都是以毫秒计的。
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;
这样就退出了循环。