当前位置: 首页 > news >正文

【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 Destination48目的MAC地址。
发送ARP请求时,为广播的MAC地址,FF-FF-FF-FF-FF-FF。
Ethernet Address of Sender48源MAC地址。
Frame Type16表示后面数据的类型。
对于ARP请求或应答来说,该字段的值为0x0806。
Hardware Type16表示硬件地址的类型。
对于以太网,该类型的值为“1”。
Protocol Type16表示发送方要映射的协议地址类型。
对于IP地址,该值为0x0800。
Hardware Length8表示硬件地址的长度,单位是字节。
对于ARP请求或应答来说,该值为6。
Protocol Length8表示协议地址的长度,单位是字节。
对于ARP请求或应答来说,该值为4。
OP16操作类型:
1:ARP请求
2:ARP应答
3:RARP请求
4:RARP应答
Ethernet Address of Sender48发送方以太网地址。
这个字段和ARP报文首部的源以太网地址字段是重复信息。
IP Address of Sender32发送方的IP地址。
Ethernet Address of Destination48接收方的以太网地址。
发送ARP请求时,该处填充值为00-00-00-00-00-00。
IP Address of Destination32接收方的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网络协议栈中的关系图:

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

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函数的执行流程大致如下:

  1. 使用ArpCreateService()函数初始化ARP_SERVICE_DATA
  2. 安装gEfiArpServiceBindingProtocolGuid,对应的服务Protocol跟MNP的是一样的:
struct _EFI_SERVICE_BINDING_PROTOCOL {EFI_SERVICE_BINDING_CREATE_CHILD     CreateChild;EFI_SERVICE_BINDING_DESTROY_CHILD    DestroyChild;
};
  1. 通过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_DATAARP_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;
  • MnpChildHandleMnp:对应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模块的主要功能。

  • ChildrenListChildrenNumberArpServiceBindingCreateChild()创建的ARP子项的链表。

  • PendingRequestTable:处理ARP的重试。

  • DeniedCacheTableResolvedCacheTable: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章节中已经介绍过。
  • SwAddressLengthStationAddress:表示IP地址和长度。
  • EntryTimeOut:APR缓存的过期时间。
  • RetryCountRetryTimeOut: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()的实现主要包含以下的内容:

  1. 一系列的基础内容判断。
  2. 判断是否在DeniedCacheTable中,如果是就不处理这个包。
  3. 判断IP是否一致,如果是一致的,表示正是本ARP需要处理的,才会有后面的操作。
  4. 判断是否在ResolvedCacheTable中,表示已经处理过了的,如果不是,则增加。

由于MNP一直在接收数据,再加上这个ARP事件的处理,所以BIOS可以处理外部网络的ARP请求。

ArpTimerHandler

用来处理ARP_SERVICE_DATA中的PendingRequestTableDeniedCacheTableResolvedCacheTable。第一个和后面两个的处理方式是不同的,第一个的重点是重发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_DATADeniedCacheTable或者ResolvedCacheTable表中。其它的成员函数,比如FIndDeleteFlush等,也都是这些表的操作,这里不再过多介绍。

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是一个开源的计算机视觉库&#xff0c;主要用于实时的人脸检测。它利用深度学习技术&#xff0c;特别是卷积神经网络&#xff08;CNN&#xff09;&#xff0c;实现了高精度的脸部定位…...

2024年海外推广怎么做?

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

Redis分布式锁--java实现

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

好消息,Linux Kernel 6.7正式发布!

据有关资料显示&#xff0c;该版本是有史以来合并数最多的版本之一&#xff0c;包含 17k 个非合并 commit&#xff0c;实际合并的超过1K个。 那么该版本主要有哪边变化呢&#xff1f;下面我来一一列举一下&#xff1a; Bcachefs文件系统已被合并到主线内核&#xff0c;这是一款…...

【k8s】Kubernetes 声明式 API、命令式

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

解锁营销新高度:幽灵鲨CRM推广平台线索对接功能详解

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

uniapp 创建组件

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

Linux--部署 Tomcat 及其负载均衡

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

影像组学介绍

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

什么是云服务器?云服务器的工作原理是介绍

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云百科aliyunbai…...

【前后端的那些事】前后端环境搭建+树形结构表格实现

文章目录 1. 前后端项目环境搭建2. table-tree2.1 后端准备2.2 前端准备 前言&#xff1a;最近写项目&#xff0c;发现了一些很有意思的功能&#xff0c;想写文章&#xff0c;录视频把这些内容记录下。但这些功能太零碎&#xff0c;如果为每个功能都单独搭建一个项目&#xff0…...

PHP版学校教务管理系统源码带文字安装教程

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

前端背景收集之烟花背景

文章目录 &#x1f412;个人主页&#x1f3c5;Vue项目常用组件模板仓库&#x1f4d6;前言&#xff1a;&#x1f380;源码如下&#xff1a; &#x1f412;个人主页 &#x1f3c5;Vue项目常用组件模板仓库 &#x1f4d6;前言&#xff1a; 本篇博客主要提供前端背景收集之烟花背景…...

PCL 格网法计算点云的占地面积

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

《设计模式的艺术》笔记 - 面向对象设计原则

1、单一职责原则 一个类只负责单一功能领域中的相应职责。 2、开闭原则 一个软件实体应当对扩展开放&#xff0c;对修改关闭。即软件实体应当尽量在不修改原有代码的情况下进行扩展。 3、里氏代换原则 所有引用基类的地方必须能透明地使用其子类的对象。即在软件中将一个基类…...

《Linux C编程实战》笔记:线程同步

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

leetcode141.环形链表

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

景联文科技:以高质量数据赋能文生图大模型

1月5日&#xff0c;在智求共赢・中国AIGC产业应用峰会暨无界AI生态合作伙伴大会上&#xff0c;中国AIGC产业联盟联合无界AI发布了《中国AIGC文生图产业白皮书2023》&#xff0c;从AIGC文生图发展历程、主流工具、产业实践以及规模预测等多个维度&#xff0c;全面揭示了中国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 中常见的设计模式有以下几种 一 单例模式&#xff08;Singleton Pattern&#xff09;&#xff1a;确保一个类只有一个实例&#xff0c;并提供全局访问点。 二 工厂模式&#xff08;Factory Pattern&#xff09;&#xff1a;使用工厂方法来创建对象&#xff0c;而不是直…...

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&#xff0c;用户都可以通过Royal TSX轻松地远程连接和管理各种服务器、计算机和网络设备。 Royal TSX for Mac提供了直观的界面和丰富的功能&#xff0c;让用户能够快速便…...

Docker 部署后端项目自动化脚本

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

MySQL从0到1全教程【2】SQL语言的通用语法及分类

1 SQL语言的通用语法格式 无论是那种数据库的产品&#xff0c;SQL语法都是通用的。 SQL语句可以单行编写也可以多行编写&#xff0c;以分号结尾。SQL语句可以使用空格或者缩进的方式来增强语句的可读性&#xff0c;空格和缩进的数量没有限制。MySQL数据库的SQL语句是不区分大…...

【npm link】Node命令中的npm link命令的使用,还有CLI全局命令的使用,开发命令行工具必不可少的部分

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;NodeJs &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续前进的勇气…...

Unity组件开发--相机跟随角色和旋转

1.相机跟随组件&#xff0c;节点&#xff1a; 2.相机跟随组件脚本&#xff1a; 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 用于创建一个对象的代理&#xff0c;将对原对象上的操作&#xff08;属性获取、赋值、函数调用…...