ARP
ARP协议说明
从这里开始涉及到的网络协议都是比较通用的了,在一般的TCP/IP四层模型中都能够看到这些内容,不过这里主要介绍的还是其在BIOS下的实现,但是在此之前还是需要先说明ARP的作用。
ARP的全称是Address Resolution Protocol,它是一种解决地址问题的协议。以目标IP为线索,用来定义下一个应该接收数据分包的网络设备对应的MAC地址。ARP只用于IPv4,不能用于IPv6(IPv6使用ICMPv6替代ARP)。ARP获取MAC地址的简单流程如下:
ARP是请求方IP通过广播发送的请求包,同一链路上所有的主机和路由器都会接收到这个包,目标地址将自己的MAC地址填入到ARP响应包返回给请求方IP。一个ARP包的格式如下:
各个参数的说明如下:
字段 | 长度(bit) | 含义 |
---|---|---|
Ethernet Address of Destination | 48 | 目的MAC地址。 发送ARP请求时,为广播的MAC地址,FF-FF-FF-FF-FF-FF。 |
Ethernet Address of Sender | 48 | 源MAC地址。 |
Frame Type | 16 | 表示后面数据的类型。 对于ARP请求或应答来说,该字段的值为0x0806。 |
Hardware Type | 16 | 表示硬件地址的类型。 对于以太网,该类型的值为“1”。 |
Protocol Type | 16 | 表示发送方要映射的协议地址类型。 对于IP地址,该值为0x0800。 |
Hardware Length | 8 | 表示硬件地址的长度,单位是字节。 对于ARP请求或应答来说,该值为6。 |
Protocol Length | 8 | 表示协议地址的长度,单位是字节。 对于ARP请求或应答来说,该值为4。 |
OP | 16 | 操作类型: 1:ARP请求 2:ARP应答 3:RARP请求 4:RARP应答 |
Ethernet Address of Sender | 48 | 发送方以太网地址。 这个字段和ARP报文首部的源以太网地址字段是重复信息。 |
IP Address of Sender | 32 | 发送方的IP地址。 |
Ethernet Address of Destination | 48 | 接收方的以太网地址。 发送ARP请求时,该处填充值为00-00-00-00-00-00。 |
IP Address of Destination | 32 | 接收方的IP地址。 |
ARP包在UEFI代码中没有一个特定的结构体来表示,不过其中的一部分还是构成了结构体:
//
// ARP packet head definition.
//
#pragma pack(1)
typedef struct {
UINT16 HwType;
UINT16 ProtoType;
UINT8 HwAddrLen;
UINT8 ProtoAddrLen;
UINT16 OpCode;
} ARP_HEAD;
#pragma pack()
而整个ARP包的构造,则位于ArpSendFrame函数中。
ARP代码综述
ARP的实现代码位于NetworkPkg\ArpDxe\ArpDxe.inf,它也是一个UEFI Driver Model,所以会安装EFI_DRIVER_BINDING_PROTOCOL
,其实现如下:
EFI_DRIVER_BINDING_PROTOCOL gArpDriverBinding = {
ArpDriverBindingSupported,
ArpDriverBindingStart,
ArpDriverBindingStop,
0xa,
NULL,
NULL
};
ARP在UEFI网络协议栈中的关系图:
ArpDriverBindingSupported
ARP依赖于MNP,所以其Supported函数实现主体如下:
EFI_STATUS
EFIAPI
ArpDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
//
// Test to see if MNP SB is installed.
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiManagedNetworkServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
}
也只是一个简单的MNP是否已经支持的判断。
ArpDriverBindingStart
Start函数的执行流程大致如下:
- 使用
ArpCreateService()
函数初始化ARP_SERVICE_DATA
。 - 安装
gEfiArpServiceBindingProtocolGuid
,对应的服务Protocol跟MNP的是一样的:
struct _EFI_SERVICE_BINDING_PROTOCOL {
EFI_SERVICE_BINDING_CREATE_CHILD CreateChild;
EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild;
};
- 通过MNP接口注册Token。
//
// OK, start to receive arp packets from Mnp.
//
Status = ArpService->Mnp->Receive (ArpService->Mnp, &ArpService->RxToken);
这里注册的Token还是在第1步中初始化的,所以以上的所有操作中,最重要的还是初始化ARP_SERVICE_DATA
的操作,后续将进一步介绍该结构体。注意ARP中没有像MNP那样的MNP_DEVICE_DATA
,这是因为ARP已经跟硬件没有关系,但是ARP中也有服务数据和实例数据,分别对应ARP_SERVICE_DATA
和ARP_INSTANCE_DATA
。ARP中的主要数据以及它们的关系,如下图所示:
ARP_SERVICE_DATA
ARP_SERVICE_DATA
结构体位于NetworkPkg\ArpDxe\ArpImpl.h,其实现如下:
//
// ARP service data structure.
//
struct _ARP_SERVICE_DATA {
UINT32 Signature;
EFI_SERVICE_BINDING_PROTOCOL ServiceBinding;
EFI_HANDLE MnpChildHandle;
EFI_HANDLE ImageHandle;
EFI_HANDLE ControllerHandle;
EFI_MANAGED_NETWORK_PROTOCOL *Mnp;
EFI_MANAGED_NETWORK_CONFIG_DATA MnpConfigData;
EFI_MANAGED_NETWORK_COMPLETION_TOKEN RxToken;
EFI_SIMPLE_NETWORK_MODE SnpMode;
UINTN ChildrenNumber;
LIST_ENTRY ChildrenList;
LIST_ENTRY PendingRequestTable;
LIST_ENTRY DeniedCacheTable;
LIST_ENTRY ResolvedCacheTable;
EFI_EVENT PeriodicTimer;
};
该结构体的初始化在ArpCreateService()
函数中,除了初始化ARP_SERVICE_DATA
之外,还有一个很重要的代码是:
//
// Create a MNP child instance.
//
Status = NetLibCreateServiceChild (
ControllerHandle,
ImageHandle,
&gEfiManagedNetworkServiceBindingProtocolGuid,
&ArpService->MnpChildHandle
);
完成这一步操作之后,MNP服务才会创建子项,才会安装EFI_MANAGED_NETWORK_PROTOCOL
供后续ARP使用。
下面介绍其中比较重要的成员:
ServiceBinding
:对应ARP的服务Protocol,由于APR依赖的是MNP的gEfiManagedNetworkServiceBindingProtocolGuid
,而MNP中可以有多个服务,因此ARP中也可能有多个。对应的实现函数:
//
// Init the servicebinding protocol members.
//
ArpService->ServiceBinding.CreateChild = ArpServiceBindingCreateChild;
ArpService->ServiceBinding.DestroyChild = ArpServiceBindingDestroyChild;
MnpChildHandle
、Mnp
:对应MNP中的EFI_MANAGED_NETWORK_PROTOCOL
及其所在的Handle,这个Handle上面还有gEfiManagedNetworkServiceBindingProtocolGuid
对应的服务Protocol。MnpConfigData
:MNP的配置参数,其值是固定的:
//
// Set the Mnp config parameters.
//
ArpService->MnpConfigData.ReceivedQueueTimeoutValue = 0;
ArpService->MnpConfigData.TransmitQueueTimeoutValue = 0;
ArpService->MnpConfigData.ProtocolTypeFilter = ARP_ETHER_PROTO_TYPE;
ArpService->MnpConfigData.EnableUnicastReceive = TRUE;
ArpService->MnpConfigData.EnableMulticastReceive = FALSE;
ArpService->MnpConfigData.EnableBroadcastReceive = TRUE;
ArpService->MnpConfigData.EnablePromiscuousReceive = FALSE;
ArpService->MnpConfigData.FlushQueuesOnReset = TRUE;
ArpService->MnpConfigData.EnableReceiveTimestamps = FALSE;
ArpService->MnpConfigData.DisableBackgroundPolling = FALSE;
之后会用这些值来配置一次MNP:
//
// Configure the Mnp child.
//
Status = ArpService->Mnp->Configure (ArpService->Mnp, &ArpService->MnpConfigData);
这一点很重要,因为MNP在接收到数据之后会根据这些值来确定是否需要回调ARP的处理函数。
-
RxToken
:包含了ARP对MNP接收到的数据的处理,对应的处理函数是ArpOnFrameRcvd()
,它实际上包含了ARP模块的主要功能。 -
ChildrenList
、ChildrenNumber
:ArpServiceBindingCreateChild()
创建的ARP子项的链表。 -
PendingRequestTable
:处理ARP的重试。 -
DeniedCacheTable
、ResolvedCacheTable
:ARP缓存表,用来缓存IP-MAC的对应关系,避免需要一直使用ARP来获取指定IP对应的MAC地址。 -
PeriodicTimer
:处理ARP心跳的定时事件:
//
// Create the Arp heartbeat timer.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
ArpTimerHandler,
ArpService,
&ArpService->PeriodicTimer
);
它的主要作用就是ARP包的重试和缓存处理,后面将进一步介绍。
ARP_INSTANCE_DATA
ARP_INSTANCE_DATA
结构体位于NetworkPkg\ArpDxe\ArpImpl.h,其结构体如下:
//
// ARP instance context data structure.
//
typedef struct {
UINT32 Signature;
ARP_SERVICE_DATA *ArpService;
EFI_HANDLE Handle;
EFI_ARP_PROTOCOL ArpProto;
LIST_ENTRY List;
EFI_ARP_CONFIG_DATA ConfigData;
BOOLEAN Configured;
BOOLEAN InDestroy;
} ARP_INSTANCE_DATA;
它在ARP服务创建ARP子项的时候生成,对应的接口是ArpService->ServiceBinding.CreateChild()
,初始化在函数ArpInitInstance()
中:
VOID
ArpInitInstance (
IN ARP_SERVICE_DATA *ArpService,
OUT ARP_INSTANCE_DATA *Instance
)
{
NET_CHECK_SIGNATURE (ArpService, ARP_SERVICE_DATA_SIGNATURE);
Instance->Signature = ARP_INSTANCE_DATA_SIGNATURE;
Instance->ArpService = ArpService;
CopyMem (&Instance->ArpProto, &mEfiArpProtocolTemplate, sizeof (Instance->ArpProto));
Instance->Configured = FALSE;
Instance->InDestroy = FALSE;
InitializeListHead (&Instance->List);
}
其中比较重要的成员有:
-
ArpService
:创建子项的那个服务对应的数据。 -
ArpProto
:ARP操作接口:
//
// Global variable of EFI ARP Protocol Interface.
//
EFI_ARP_PROTOCOL mEfiArpProtocolTemplate = {
ArpConfigure,
ArpAdd,
ArpFind,
ArpDelete,
ArpFlush,
ArpRequest,
ArpCancel
};
-
Handle
:安装ArpProto
的Handle。 -
List
:对应到ARP_SERVICE_DATA
中的ChildrenList
,两者构成链表。 -
ConfigData
:ARP的配置参数,在EFI_ARP_CONFIG_DATA中会进一步介绍。 -
Configured
:用来表示ARP是否已经配置。 -
InDestroy
:用于防止重入的标志。
EFI_ARP_CONFIG_DATA
ARP也需要配置,所以存在这个结构体,其实现如下:
typedef struct {
///
/// 16-bit protocol type number in host byte order.
///
UINT16 SwAddressType;
///
/// The length in bytes of the station's protocol address to register.
///
UINT8 SwAddressLength;
///
/// The pointer to the first byte of the protocol address to register. For
/// example, if SwAddressType is 0x0800 (IP), then
/// StationAddress points to the first byte of this station's IP
/// address stored in network byte order.
///
VOID *StationAddress;
///
/// The timeout value in 100-ns units that is associated with each
/// new dynamic ARP cache entry. If it is set to zero, the value is
/// implementation-specific.
///
UINT32 EntryTimeOut;
///
/// The number of retries before a MAC address is resolved. If it is
/// set to zero, the value is implementation-specific.
///
UINT32 RetryCount;
///
/// The timeout value in 100-ns units that is used to wait for the ARP
/// reply packet or the timeout value between two retries. Set to zero
/// to use implementation-specific value.
///
UINT32 RetryTimeOut;
} EFI_ARP_CONFIG_DATA;
SwAddressType
:对应以太网帧的类型,在MNP章节中已经介绍过。SwAddressLength
、StationAddress
:表示IP地址和长度。EntryTimeOut
:APR缓存的过期时间。RetryCount
、RetryTimeOut
:ARP重试次数和超时时间。
ARP_CACHE_ENTRY
该结构体用来存放每一个ARP缓存项,其中的重点就是IP和MAC的对应。其结构体如下:
typedef union {
UINT8 ProtoAddress[ARP_MAX_PROTOCOL_ADDRESS_LEN];
UINT8 HwAddress[ARP_MAX_HARDWARE_ADDRESS_LEN];
} NET_ARP_ADDRESS_UNION;
//
// ARP address structure in an ARP packet.
//
typedef struct {
UINT16 Type;
UINT8 Length;
UINT8 *AddressPtr;
NET_ARP_ADDRESS_UNION Buffer;
} NET_ARP_ADDRESS;
//
// Enumeration for ARP address type.
//
typedef enum {
Hardware,
Protocol
} ARP_ADDRESS_TYPE;
//
// ARP cache entry definition.
//
typedef struct {
LIST_ENTRY List;
UINT32 RetryCount;
UINT32 DefaultDecayTime;
UINT32 DecayTime;
UINT32 NextRetryTime;
NET_ARP_ADDRESS Addresses[2];
LIST_ENTRY UserRequestList;
} ARP_CACHE_ENTRY;
虽然有几层结构体的包装,但是可以看到最重要的还是Addresses
,它的两个成员分别是:
//
// Enumeration for ARP address type.
//
typedef enum {
Hardware,
Protocol
} ARP_ADDRESS_TYPE;
一个表示IP,另一个表示MAC地址。
ArpSendFrame
ArpSendFrame()
可以说是ARP驱动中最重要的函数,其实现如下:
VOID
ArpSendFrame (
IN ARP_INSTANCE_DATA *Instance,
IN ARP_CACHE_ENTRY *CacheEntry,
IN UINT16 ArpOpCode
)
{
//
// Allocate memory for the TxToken.
//
TxToken = AllocatePool (sizeof (EFI_MANAGED_NETWORK_COMPLETION_TOKEN));
TxToken->Event = NULL;
TxData = NULL;
Packet = NULL;
//
// Create the event for this TxToken.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ArpOnFrameSent,
(VOID *)TxToken,
&TxToken->Event
);
//
// Allocate memory for the TxData used in the TxToken.
//
TxData = AllocatePool (sizeof (EFI_MANAGED_NETWORK_TRANSMIT_DATA));
ArpService = Instance->ArpService;
SnpMode = &ArpService->SnpMode;
ConfigData = &Instance->ConfigData;
//
// Calculate the buffer length for this arp frame.
//
TotalLength = SnpMode->MediaHeaderSize + sizeof (ARP_HEAD) +
2 * (ConfigData->SwAddressLength + SnpMode->HwAddressSize);
//
// Allocate buffer for the arp frame.
//
// 这里开始构建ARP包,其具体的内容前面已经介绍过
TmpPtr = Packet;
//
// The destination MAC address.
//
// 根据是接受还是发送数据包,ARP包的内容会不同
if (ArpOpCode == ARP_OPCODE_REQUEST) {
CopyMem (TmpPtr, &SnpMode->BroadcastAddress, SnpMode->HwAddressSize);
} else {
CopyMem (
TmpPtr,
CacheEntry->Addresses[Hardware].AddressPtr,
SnpMode->HwAddressSize
);
}
TmpPtr += SnpMode->HwAddressSize;
//
// The source MAC address.
//
CopyMem (TmpPtr, &SnpMode->CurrentAddress, SnpMode->HwAddressSize);
TmpPtr += SnpMode->HwAddressSize;
//
// The ethernet protocol type.
//
*(UINT16 *)TmpPtr = HTONS (ARP_ETHER_PROTO_TYPE);
TmpPtr += 2;
//
// The ARP Head.
//
ArpHead = (ARP_HEAD *)TmpPtr;
ArpHead->HwType = HTONS ((UINT16)SnpMode->IfType);
ArpHead->ProtoType = HTONS (ConfigData->SwAddressType);
ArpHead->HwAddrLen = (UINT8)SnpMode->HwAddressSize;
ArpHead->ProtoAddrLen = ConfigData->SwAddressLength;
ArpHead->OpCode = HTONS (ArpOpCode);
TmpPtr += sizeof (ARP_HEAD);
//
// The sender hardware address.
//
CopyMem (TmpPtr, &SnpMode->CurrentAddress, SnpMode->HwAddressSize);
TmpPtr += SnpMode->HwAddressSize;
//
// The sender protocol address.
//
CopyMem (TmpPtr, ConfigData->StationAddress, ConfigData->SwAddressLength);
TmpPtr += ConfigData->SwAddressLength;
//
// The target hardware address.
//
CopyMem (
TmpPtr,
CacheEntry->Addresses[Hardware].AddressPtr,
SnpMode->HwAddressSize
);
TmpPtr += SnpMode->HwAddressSize;
//
// The target protocol address.
//
CopyMem (
TmpPtr,
CacheEntry->Addresses[Protocol].AddressPtr,
ConfigData->SwAddressLength
);
//
// Set all the fields of the TxData.
//
TxData->DestinationAddress = NULL;
TxData->SourceAddress = NULL;
TxData->ProtocolType = 0;
TxData->DataLength = TotalLength - SnpMode->MediaHeaderSize;
TxData->HeaderLength = (UINT16)SnpMode->MediaHeaderSize;
TxData->FragmentCount = 1;
// 真正的数据在这里
TxData->FragmentTable[0].FragmentBuffer = Packet;
TxData->FragmentTable[0].FragmentLength = TotalLength;
//
// Associate the TxData with the TxToken.
//
TxToken->Packet.TxData = TxData;
TxToken->Status = EFI_NOT_READY;
//
// Send out this arp packet by Mnp.
//
Status = ArpService->Mnp->Transmit (ArpService->Mnp, TxToken);
}
ArpSendFrame()
会根据ARP包是接受后的反馈还是直接的发送进行区分,产生不同的ARP包,最终通过MNP的接口发送出去。
ARP事件
ArpOnFrameRcvd
ArpOnFrameRcvd
是Token中的回调函数,其创建代码如下:
//
// Create the event used in the RxToken.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ArpOnFrameRcvd,
ArpService,
&ArpService->RxToken.Event
);
注意这个不是定时事件,而是由其它的代码触发的,主要就是MNP,也就是说它是MNP接收到数据之后会执行的回调,所以它就可以用来接收ARP包并进行解析和处理,它的第一层实现很简单:
VOID
EFIAPI
ArpOnFrameRcvd (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Request ArpOnFrameRcvdDpc as a DPC at TPL_CALLBACK
//
QueueDpc (TPL_CALLBACK, ArpOnFrameRcvdDpc, Context);
}
这里可以看到对DPC的使用。ArpOnFrameRcvdDpc()
的实现主要包含以下的内容:
- 一系列的基础内容判断。
- 判断是否在
DeniedCacheTable
中,如果是就不处理这个包。 - 判断IP是否一致,如果是一致的,表示正是本ARP需要处理的,才会有后面的操作。
- 判断是否在
ResolvedCacheTable
中,表示已经处理过了的,如果不是,则增加。
由于MNP一直在接收数据,再加上这个ARP事件的处理,所以BIOS可以处理外部网络的ARP请求。
ArpTimerHandler
用来处理ARP_SERVICE_DATA
中的PendingRequestTable
、DeniedCacheTable
和ResolvedCacheTable
。第一个和后面两个的处理方式是不同的,第一个的重点是重发ARP包:
ArpSendFrame (RequestContext->Instance, CacheEntry, ARP_OPCODE_REQUEST);
后面两个的重点是链表操作:
RemoveEntryList (&CacheEntry->List);
注意这个定时事件在ARP服务创建之后就启动了:
//
// Start the heartbeat timer.
//
Status = gBS->SetTimer (
ArpService->PeriodicTimer,
TimerPeriodic,
ARP_PERIODIC_TIMER_INTERVAL // 500毫秒
);
它完成重试、缓存清理等操作。
ARP的使用
ARP的使用包括两个部分,第一部分是响应其它网络的ARP包,这个部分主要就是ArpOnFrameRcvd的实现;第二部分就是自己发送ARP包,来获取指定IP对应的MAC地址。关于第二部分,主要在IP4和PXE等模块中,以前者为例,主要在函数Ip4SendFrame()
中:
//
// First check whether this binding is in the ARP cache.
//
NextHop = HTONL (NextHop);
Status = Arp->Request (Arp, &NextHop, NULL, &Token->DstMac);
// 中间略
//
// First frame to NextHop, issue an asynchronous ARP requests
//
ArpQue = Ip4CreateArpQue (Interface, NextHop);
Status = Arp->Request (Arp, &ArpQue->Ip, ArpQue->OnResolved, ArpQue->Mac.Addr);
InsertHeadList (&ArpQue->Frames, &Token->Link);
InsertHeadList (&Interface->ArpQues, &ArpQue->Link);
return EFI_SUCCESS;
这里主要调用的是EFI_ARP_PROTOCOL
中的Request
成员函数,后面会进一步介绍。
EFI_ARP_PROTOCOL
该Protocol的结构体如下:
///
/// ARP is used to resolve local network protocol addresses into
/// network hardware addresses.
///
struct _EFI_ARP_PROTOCOL {
EFI_ARP_CONFIGURE Configure;
EFI_ARP_ADD Add;
EFI_ARP_FIND Find;
EFI_ARP_DELETE Delete;
EFI_ARP_FLUSH Flush;
EFI_ARP_REQUEST Request;
EFI_ARP_CANCEL Cancel;
};
对应的实现在NetworkPkg\ArpDxe\ArpImpl.c:
EFI_ARP_PROTOCOL mEfiArpProtocolTemplate = {
ArpConfigure,
ArpAdd,
ArpFind,
ArpDelete,
ArpFlush,
ArpRequest,
ArpCancel
};
后面会介绍这些函数的实现。
Arp.Configure
对应的实现是ArpConfigure
,其代码实现:
EFI_STATUS
EFIAPI
ArpConfigure (
IN EFI_ARP_PROTOCOL *This,
IN EFI_ARP_CONFIG_DATA *ConfigData OPTIONAL
)
{
//
// Configure this instance, the ConfigData has already passed the basic checks.
//
Status = ArpConfigureInstance (Instance, ConfigData);
}
最终的实现在ArpConfigureInstance()
:
EFI_STATUS
ArpConfigureInstance (
IN ARP_INSTANCE_DATA *Instance,
IN EFI_ARP_CONFIG_DATA *ConfigData OPTIONAL
)
{
if (ConfigData != NULL) {
// 如果已经配置过了,那么就是更新配置
if (Instance->Configured) {
//
// The instance is configured, check the unchangeable fields.
//
if ((OldConfigData->SwAddressType != ConfigData->SwAddressType) ||
(OldConfigData->SwAddressLength != ConfigData->SwAddressLength) ||
(CompareMem (
OldConfigData->StationAddress,
ConfigData->StationAddress,
OldConfigData->SwAddressLength
) != 0))
{
//
// Deny the unallowed changes.
//
return EFI_ACCESS_DENIED;
}
} else {
//
// The instance is not configured.
//
if (ConfigData->SwAddressType == IPV4_ETHER_PROTO_TYPE) {
CopyMem (&Ip, ConfigData->StationAddress, sizeof (IP4_ADDR));
if (IP4_IS_UNSPECIFIED (Ip) || IP4_IS_LOCAL_BROADCAST (Ip)) {
//
// The station address should not be zero or broadcast address.
//
return EFI_INVALID_PARAMETER;
}
}
//
// Save the configuration.
//
CopyMem (OldConfigData, ConfigData, sizeof (*OldConfigData));
OldConfigData->StationAddress = AllocatePool (OldConfigData->SwAddressLength);
if (OldConfigData->StationAddress == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Save the StationAddress.
//
CopyMem (
OldConfigData->StationAddress,
ConfigData->StationAddress,
OldConfigData->SwAddressLength
);
//
// Set the state to configured.
//
Instance->Configured = TRUE;
}
//
// Use the implementation specific values if the following field is zero.
//
OldConfigData->EntryTimeOut = (ConfigData->EntryTimeOut == 0) ?
ARP_DEFAULT_TIMEOUT_VALUE : ConfigData->EntryTimeOut;
OldConfigData->RetryCount = (ConfigData->RetryCount == 0) ?
ARP_DEFAULT_RETRY_COUNT : ConfigData->RetryCount;
OldConfigData->RetryTimeOut = (ConfigData->RetryTimeOut == 0) ?
ARP_DEFAULT_RETRY_INTERVAL : ConfigData->RetryTimeOut;
} else {
//
// Reset the configuration.
//
if (Instance->Configured) {
//
// Cancel the arp requests issued by this instance.
//
Instance->ArpProto.Cancel (&Instance->ArpProto, NULL, NULL);
//
// Free the buffer previously allocated to hold the station address.
//
FreePool (OldConfigData->StationAddress);
}
Instance->Configured = FALSE;
}
}
如果入参ConfigData
的值是NULL,则表示重置配置;否则就会根据入参进行配置。
Arp.Add
对应的实现是ArpAdd
,该函数最终会将IP和MAC地址写入到ARP_SERVICE_DATA
的DeniedCacheTable
或者ResolvedCacheTable
表中。其它的成员函数,比如FInd
、Delete
、Flush
等,也都是这些表的操作,这里不再过多介绍。
Arp.Request
对应的实现是ArpRequest
,它会去获取指定IP的MAC地址:
EFI_STATUS
EFIAPI
ArpRequest (
IN EFI_ARP_PROTOCOL *This,
IN VOID *TargetSwAddress OPTIONAL,
IN EFI_EVENT ResolvedEvent OPTIONAL,
OUT VOID *TargetHwAddress
)
{
if (!Instance->Configured) {
return EFI_NOT_STARTED;
}
Status = EFI_SUCCESS;
ArpService = Instance->ArpService;
SnpMode = &ArpService->SnpMode;
if ((TargetSwAddress == NULL) ||
((Instance->ConfigData.SwAddressType == IPV4_ETHER_PROTO_TYPE) &&
IP4_IS_LOCAL_BROADCAST (*((UINT32 *)TargetSwAddress))))
{
//
// Return the hardware broadcast address.
//
CopyMem (TargetHwAddress, &SnpMode->BroadcastAddress, SnpMode->HwAddressSize);
goto SIGNAL_USER;
}
if ((Instance->ConfigData.SwAddressType == IPV4_ETHER_PROTO_TYPE) &&
IP4_IS_MULTICAST (NTOHL (*((UINT32 *)TargetSwAddress))))
{
//
// If the software address is an IPv4 multicast address, invoke Mnp to
// resolve the address.
//
Status = ArpService->Mnp->McastIpToMac (
ArpService->Mnp,
FALSE,
TargetSwAddress,
TargetHwAddress
);
goto SIGNAL_USER;
}
HardwareAddress.Type = SnpMode->IfType;
HardwareAddress.Length = (UINT8)SnpMode->HwAddressSize;
HardwareAddress.AddressPtr = NULL;
ProtocolAddress.Type = Instance->ConfigData.SwAddressType;
ProtocolAddress.Length = Instance->ConfigData.SwAddressLength;
ProtocolAddress.AddressPtr = TargetSwAddress;
//
// Initialize the TargetHwAddress to a zero address.
//
ZeroMem (TargetHwAddress, SnpMode->HwAddressSize);
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
//
// Check whether the software address is in the denied table.
//
CacheEntry = ArpFindDeniedCacheEntry (ArpService, &ProtocolAddress, NULL);
if (CacheEntry != NULL) {
Status = EFI_ACCESS_DENIED;
goto UNLOCK_EXIT;
}
//
// Check whether the software address is already resolved.
//
CacheEntry = ArpFindNextCacheEntryInTable (
&ArpService->ResolvedCacheTable,
NULL,
ByProtoAddress,
&ProtocolAddress,
NULL
);
if (CacheEntry != NULL) {
//
// Resolved, copy the address into the user buffer.
//
CopyMem (
TargetHwAddress,
CacheEntry->Addresses[Hardware].AddressPtr,
CacheEntry->Addresses[Hardware].Length
);
goto UNLOCK_EXIT;
}
//
// Create a request context for this arp request.
//
RequestContext = AllocatePool (sizeof (USER_REQUEST_CONTEXT));
RequestContext->Instance = Instance;
RequestContext->UserRequestEvent = ResolvedEvent;
RequestContext->UserHwAddrBuffer = TargetHwAddress;
InitializeListHead (&RequestContext->List);
//
// Check whether there is a same request.
//
CacheEntry = ArpFindNextCacheEntryInTable (
&ArpService->PendingRequestTable,
NULL,
ByProtoAddress,
&ProtocolAddress,
NULL
);
if (CacheEntry != NULL) {
CacheEntry->NextRetryTime = Instance->ConfigData.RetryTimeOut;
CacheEntry->RetryCount = Instance->ConfigData.RetryCount;
} else {
//
// Allocate a cache entry for this request.
//
CacheEntry = ArpAllocCacheEntry (Instance);
if (CacheEntry == NULL) {
DEBUG ((DEBUG_ERROR, "ArpRequest: Allocate memory for CacheEntry failed.\n"));
FreePool (RequestContext);
Status = EFI_OUT_OF_RESOURCES;
goto UNLOCK_EXIT;
}
//
// Fill the software address.
//
ArpFillAddressInCacheEntry (CacheEntry, &HardwareAddress, &ProtocolAddress);
//
// Add this entry into the PendingRequestTable.
//
InsertTailList (&ArpService->PendingRequestTable, &CacheEntry->List);
}
//
// Link this request context into the cache entry.
//
InsertHeadList (&CacheEntry->UserRequestList, &RequestContext->List);
//
// Send out the ARP Request frame.
//
ArpSendFrame (Instance, CacheEntry, ARP_OPCODE_REQUEST);
Status = EFI_NOT_READY;
UNLOCK_EXIT:
gBS->RestoreTPL (OldTpl);
SIGNAL_USER:
if ((ResolvedEvent != NULL) && (Status == EFI_SUCCESS)) {
gBS->SignalEvent (ResolvedEvent);
//
// Dispatch the DPC queued by the NotifyFunction of ResolvedEvent.
//
DispatchDpc ();
}
}
这里用到了前面介绍的ArpSendFrame。注意这里并不会简单就返回结果,理由还是跟之前说的一样,CPU的执行速度会快于网卡,从ARP代码示例可以看到真正有效的用法。此外,使用该函数还可以获取广播和多播地址。
ARP代码示例
通过ARP接口获取指定IP的MAC地址是一种常用的做法,下面是一个示例代码(位于BeniPkg\DynamicCommand\TestDynamicCommand\TestArp.c):
VOID
CheckArp (
IN EFI_HANDLE Handle,
IN CONST CHAR16 *SrcString,
IN CONST CHAR16 *DstString
)
{
EFI_STATUS Status = EFI_ABORTED;
EFI_ARP_PROTOCOL *Arp = NULL;
EFI_HANDLE ArpHandle = NULL;
EFI_IPv4_ADDRESS SrcIp;
EFI_IPv4_ADDRESS DestIp;
IP4_ADDR IpAddr;
EFI_MAC_ADDRESS Mac;
EFI_MAC_ADDRESS ZeroMac;
EFI_ARP_CONFIG_DATA ArpConfig;
EFI_EVENT ResolvedEvent;
BOOLEAN IsResolved = FALSE;
ZeroMem (&Mac, sizeof (EFI_MAC_ADDRESS));
ZeroMem (&ZeroMac, sizeof (EFI_MAC_ADDRESS));
Print (L"Resolving IP %s ...\r\n", DstString);
Status = NetLibCreateServiceChild (
Handle,
Handle,
&gEfiArpServiceBindingProtocolGuid,
&ArpHandle
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
}
Status = gBS->OpenProtocol (
ArpHandle,
&gEfiArpProtocolGuid,
(VOID **)(&Arp),
Handle,
Handle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
}
Status = NetLibStrToIp4 (SrcString, &SrcIp);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
} else {
Print (L"Source IP : %d.%d.%d.%d\r\n",
SrcIp.Addr[0],
SrcIp.Addr[1],
SrcIp.Addr[2],
SrcIp.Addr[3]
);
}
Status = NetLibStrToIp4 (DstString, &DestIp);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
} else {
Print (L"Destination IP: %d.%d.%d.%d\r\n",
DestIp.Addr[0],
DestIp.Addr[1],
DestIp.Addr[2],
DestIp.Addr[3]
);
}
IpAddr = EFI_NTOHL (SrcIp);
IpAddr = HTONL (IpAddr);
ArpConfig.SwAddressType = 0x0800;
ArpConfig.SwAddressLength = 4;
ArpConfig.StationAddress = &IpAddr;
ArpConfig.EntryTimeOut = 0;
ArpConfig.RetryCount = 0;
ArpConfig.RetryTimeOut = 0;
Status = Arp->Configure (Arp, NULL);
Status = Arp->Configure (Arp, &ArpConfig);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
}
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
CheckIfResolved,
&IsResolved,
&ResolvedEvent
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
}
Status = Arp->Request (Arp, &DestIp, ResolvedEvent, &Mac);
if (EFI_ERROR (Status) && (Status != EFI_NOT_READY)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return;
}
while (!IsResolved) {
if (CompareMem (&Mac, &ZeroMac, sizeof (EFI_MAC_ADDRESS)) != 0) {
break;
}
}
Print (L"MAC: %02x:%02x:%02x:%02x:%02x:%02x\r\n",
Mac.Addr[0],
Mac.Addr[1],
Mac.Addr[2],
Mac.Addr[3],
Mac.Addr[4],
Mac.Addr[5]
);
}
代码中的重点主要是两个,一个是ARP的配置,另一个是ARP的请求,执行结果如下: