【UEFI基础】EDK网络框架(ARP)
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的请求,执行结果如下:
相关文章:

【UEFI基础】EDK网络框架(ARP)
ARP ARP协议说明 从这里开始涉及到的网络协议都是比较通用的了,在一般的TCP/IP四层模型中都能够看到这些内容,不过这里主要介绍的还是其在BIOS下的实现,但是在此之前还是需要先说明ARP的作用。 ARP的全称是Address Resolution Protocol&am…...

Linux进阶课:目录(文件夹)与文件操作
1、ls与cat的区别是是什么? 答:ls命令的含义是list,显示当前目录中内容。不加参数时它显示当前目录中除隐藏文件外的所有文件及目录的名字。 cat命令是linux下的一个文本输出命令,通常是用于查看某个文件的内容的。 2、[abc]这个…...

Flink自定义Source模拟数据流
maven依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…...

[易语言]使用易语言部署工业级人脸检测模型
【框架地址】 https://github.com/ShiqiYu/libfacedetection 【算法介绍】 Libfacedetection是一个开源的计算机视觉库,主要用于实时的人脸检测。它利用深度学习技术,特别是卷积神经网络(CNN),实现了高精度的脸部定位…...

2024年海外推广怎么做?
要说如何做海外推广,绝大多数的外贸小伙伴都能说上一些,但是大部分人对于推广系统知识略知一二,没有构建一个系统化的知识框架。 海外推广的几大步骤 1.定策略 做海外推广前,我们需要制定一套营销策略,需要去定义一…...

Redis分布式锁--java实现
文章目录 Redis分布式锁方案:SETNX EXPIRE基本原理比较好的实现会产生四个问题 几种解决原子性的方案方案:SETNX value值是(系统时间过期时间)方案:使用Lua脚本(包含SETNX EXPIRE两条指令)方案:SET的扩展…...

好消息,Linux Kernel 6.7正式发布!
据有关资料显示,该版本是有史以来合并数最多的版本之一,包含 17k 个非合并 commit,实际合并的超过1K个。 那么该版本主要有哪边变化呢?下面我来一一列举一下: Bcachefs文件系统已被合并到主线内核,这是一款…...

【k8s】Kubernetes 声明式 API、命令式
1. 资源管理方式: 1>. 命令式对象管理∶直接使用命令去操作kubernetes资源 kubectl run nginx-pod --imagenginx:1.17.1 --port802>. 命令式对象配置∶通过命令配置和配置文件去操作kubernetes资源 kubectl create/patch -f nginx-pod.yaml3>. 声明式对…...

解锁营销新高度:幽灵鲨CRM推广平台线索对接功能详解
数字营销时代,线索对接是推动业务增长的关键。你是否为线索分布在不同的平台而来回切换?你是否为无法及时联系客户而错失商机?幽灵鲨CRM系统作为一款领先的客户关系管理解决方案,不仅实现了对主流推广平台的全面对接,更…...

uniapp 创建组件
组件:用于将某个功能的 HTML、CSS、JS 封装到一个文件中,提高代码的复用性和可维护性。 创建组件 一、在根目录中创建 components 文件夹,右键点击新建组件。 二、输入组件名称、选择默认模板、点击创建组件。 三、在组件中正常编写内容即可…...

Linux--部署 Tomcat 及其负载均衡
1.案例前置知识点 1)Tomcat简介 名称由来:Tomcat最初是由 Sun的软件构架师詹姆斯邓肯戴维森开发的。后来他帮助将其变 为开源项目,并由Sun贡献给Apache软件基金会。由于大部分开源项目OReilly都会出一本相关的 书,并且将其封面设…...

影像组学介绍
影像组学介绍 1 影像组学介绍2 具体提取影像组学方法流程及工具代码:2.1 影像数据获取2.2 感兴趣区域分割2.3 特征提取与降维选择2.3.1 特征提取:2.3.2 特征降维(特征选择) 2.4 建模分析:2.5 结果分析 参考: 1 影像组学介绍 其实…...

什么是云服务器?云服务器的工作原理是介绍
阿里云服务器ECS英文全程Elastic Compute Service,云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务,阿里云提供多种云服务器ECS实例规格,如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等,阿里云百科aliyunbai…...

【前后端的那些事】前后端环境搭建+树形结构表格实现
文章目录 1. 前后端项目环境搭建2. table-tree2.1 后端准备2.2 前端准备 前言:最近写项目,发现了一些很有意思的功能,想写文章,录视频把这些内容记录下。但这些功能太零碎,如果为每个功能都单独搭建一个项目࿰…...

PHP版学校教务管理系统源码带文字安装教程
PHP版学校教务管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 系统介绍: 后台权限控制:支持多个管理员,学生管理,学生成绩,教师管理,文章管理&#x…...

前端背景收集之烟花背景
文章目录 🐒个人主页🏅Vue项目常用组件模板仓库📖前言:🎀源码如下: 🐒个人主页 🏅Vue项目常用组件模板仓库 📖前言: 本篇博客主要提供前端背景收集之烟花背景…...

PCL 格网法计算点云的占地面积
目录 一、算法原理二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT生成的文章。 一、算法原理 该方法主要用于粗略统计机载点云的占地面积。方法原理是将点云沿 X O Y XOY...

《设计模式的艺术》笔记 - 面向对象设计原则
1、单一职责原则 一个类只负责单一功能领域中的相应职责。 2、开闭原则 一个软件实体应当对扩展开放,对修改关闭。即软件实体应当尽量在不修改原有代码的情况下进行扩展。 3、里氏代换原则 所有引用基类的地方必须能透明地使用其子类的对象。即在软件中将一个基类…...

《Linux C编程实战》笔记:线程同步
这一节主要是解决共享资源的处理。操作系统里也讲过互斥、锁之类的概念。 互斥锁 互斥锁通过锁机制来实现线程同步,同一时刻只允许一个线程执行一个关键部分的代码 一下是操作互斥锁的函数,均声明在pthread.h中。 pthread_mutex_init(初始…...

leetcode141.环形链表
题目 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#…...

景联文科技:以高质量数据赋能文生图大模型
1月5日,在智求共赢・中国AIGC产业应用峰会暨无界AI生态合作伙伴大会上,中国AIGC产业联盟联合无界AI发布了《中国AIGC文生图产业白皮书2023》,从AIGC文生图发展历程、主流工具、产业实践以及规模预测等多个维度,全面揭示了中国AIGC…...

[论文笔记] PAI-Megatron中qwen和mistral合并到Megtron-LM
一、千问 关于tokenizer的改动: 1.1、更改build_tokenizer中tokenizer类的加载。 /mnt/nas/pretrain/code/Megatron-LM/megatron/tokenizer/__init__.py 或者 tokenizer.py 在build_tokenizer.py函数中: elif args.tokenizer_type == "QwenTokenizer":assert a…...

python设计模式有哪几种
Python 中常见的设计模式有以下几种 一 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。 二 工厂模式(Factory Pattern):使用工厂方法来创建对象,而不是直…...

C语言从入门到实战——数据在内存中的存储方式
数据在内存中的存储方式 前言1. 整数在内存中的存储2. 大小端字节序和字节序判断2.1 什么是大小端2.2 为什么有大小端2.3 练习2.3.1 练习12.3.2 练习22.3.3 练习32.3.4 练习42.3.5 练习52.3.6 练习6 3. 浮点数在内存中的存储3.1 练习3.2 浮点数的存储3.2.1 浮点数存的过程3.2.2…...

高效便捷的远程管理利器——Royal TSX for Mac软件介绍
Royal TSX for Mac是一款功能强大、操作便捷的远程管理软件。无论是远程桌面、SSH、VNC、Telnet还是FTP,用户都可以通过Royal TSX轻松地远程连接和管理各种服务器、计算机和网络设备。 Royal TSX for Mac提供了直观的界面和丰富的功能,让用户能够快速便…...

Docker 部署后端项目自动化脚本
文章目录 开机自启动docker打包后端项目Dockerfile文件脚本文件使用 开机自启动docker systemctl enable dockersystemctl is-enabled docker打包后端项目 这里的项目位置是target同级目录 1.在项目下面新建一个bin目录 新建一个package.txt 写入下方代码后 后缀改为.bat ec…...

MySQL从0到1全教程【2】SQL语言的通用语法及分类
1 SQL语言的通用语法格式 无论是那种数据库的产品,SQL语法都是通用的。 SQL语句可以单行编写也可以多行编写,以分号结尾。SQL语句可以使用空格或者缩进的方式来增强语句的可读性,空格和缩进的数量没有限制。MySQL数据库的SQL语句是不区分大…...

【npm link】Node命令中的npm link命令的使用,还有CLI全局命令的使用,开发命令行工具必不可少的部分
😁 作者简介:一名大四的学生,致力学习前端开发技术 ⭐️个人主页:夜宵饽饽的主页 ❔ 系列专栏:NodeJs 👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气…...

Unity组件开发--相机跟随角色和旋转
1.相机跟随组件,节点: 2.相机跟随组件脚本: using System; using System.Collections; using System.Collections.Generic; using Unity.Burst.Intrinsics; using UnityEngine; using UnityEngine.UI;public class CameraFollow : Singleton&…...

JavaScript系列——Proxy(代理)
文章目录 概要Proxy 语法handler 对象的方法Proxy 示例常用handler 对象的方法的参数handler.get()语法示例 handler.set()语法示例 使用场景验证值修正及附加属性 小结 概要 Proxy 用于创建一个对象的代理,将对原对象上的操作(属性获取、赋值、函数调用…...