【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的请求,执行结果如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/316923.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI-数学-高中-4.函数表达式特性-要变一起变

求f(x):换元法:左边代换时,右边也要同时替换,原作者视频:函数】1引导课:高中为什么用f(x)_哔哩哔哩_bilibili 1.什么是函数:给定任意一个x,都有唯一确定的y与之对应,这种x与y的关系就…

如何提高匹配的精确度(多次学习)

我们工业自动化中,视觉软件匹配,都是学习一次,比如找到轮廓,旋转360度,也就是有360个轮廓,然后到图像中去找任意角度的目标。 这样的学习并不能一而概括全。 所以,我借鉴ai的方法,…

【计算机组成原理】指令流水线的三种冒险情况(Hazards)

冒险 在计算机架构中,流水线冒险是指在指令流水线的执行过程中由于数据相关性或控制相关性而导致的一种性能问题。指令流水线是将指令执行过程划分为多个阶段,这样可以同时处理多条指令,从而提高指令执行的效率。然而,流水线执行…

阳光保险选择OceanBase稳定运行超700天

阳光保险集团成立于 2005 年 7 月,旗下拥有财产保险、人寿保险、信用保证保险、资产管理等多家专业子公司,是全球市场化企业中成长最快的集团公司之一,目前位列中国保险行业前八。随着数字化升级趋势的不断加速,很多企业产生将软硬…

Kali Linux——aircrack-ng无线教程

目录 一、准备 二、案例 1、连接usb无线网卡 2、查看网卡信息 3、开启网卡监听 4、扫描wifi信号 5、抓取握手包 6、强制断开连接 7、破解握手包 三、预防 一、准备 1、usb无线网卡(笔记本也是需要用到) 2、密码字典(Kali 系统自带…

微信小程序 全局配置||微信小程序 页面配置||微信小程序 sitemap配置

全局配置 小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。 以下是一个包含了部分常用配置选项的 app.json : {"pages": ["pages/index/index",&q…

软件设计考试相关信息

简单介绍 软考是国家人力资源和社会保障部、工业和信息化部联合组织实施的国家级考试,参加计算机软件资格考试并取得相应级别的资格证书,是各用人单位聘用计算机技术与软件专业工程师系列职务的前提。计算机软件资格考试,与会计、经济师、税…

2024年广东省安全员C证第四批(专职安全生产管理人员)证模拟考试题库及广东省安全员C证第四批(专职安全生产管理人员)理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年广东省安全员C证第四批(专职安全生产管理人员)证模拟考试题库及广东省安全员C证第四批(专职安全生产管理人员)理论考试试题是由安全生产模拟考试一点通提供&#…

科大讯飞星火大模型接入API js 部分 接口 安装注意事项

下载以下链接例子运行程序 https://xfyun-doc.xfyun.cn/static%2F16968175055332330%2Fspark_js_demo.zip 官网给的说明 准备 1.在demo中填写APPID、APISecret、APIKey,可到控制台-我的应用-大模型页面获取 2.安装nodejs 本地运行 1.打开cmd,进入dem…

CSS 下载进度条

<template><view class=btn>下载中</view></template><script></script><style>/* 设置整个页面的样式 */body {width: 100vw; /* 页面宽度为视口宽度 */background: #000000; /* 背景颜色为白色 */display: flex; /* 使用 flex…

浅析五种 React 组件设计模式

作为一名 React 开发者&#xff0c;你可能会面临下面几个问题&#xff1a; 如何构建一个高复用度性的组件&#xff0c;使其适应不同的业务场景&#xff1f;如何构建一个具有简单 API的组件&#xff0c;使其易于使用&#xff1f;如何构建一个在 UI 和功能方面具有可扩展性的组件…

四大会计假设

目录 一. 会计主体假设二. 持续经营假设三. 会计期间假设四. 货币计量假设 \quad \quad 一. 会计主体假设 \quad 会计主体: 会计工作为其服务的特定单位或组织。 会计主体的定义 1.具有一定数量的资金。 2.进行独立的生产经营或其他活动。 3.实行独立核算。 \quad 会计主体假设…

【亲测可行】如何申请并登录腾讯云免费服务器

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

5V高细分步进电机驱动芯片选型分析

单通道5V高细分步进电机GC6139 GC6106 GC6107 GC6119 GC6151 GC6236 GC8558 它们应用在摇头机&#xff0c;X,Y控制&#xff0c;聚焦控制等产品上。其中GC8558为24V H 桥驱动&#xff0c;大电流&#xff0c;具有短地短电源保护&#xff0c;限流保护等功能。

使用curl命令在Linux上进行HTTP请求

在Linux系统中&#xff0c;curl是一个非常强大的命令行工具&#xff0c;用于发送各种类型的HTTP请求。通过简单的命令&#xff0c;你可以发送GET、POST、PUT、DELETE等请求&#xff0c;以及设置请求头、处理响应等。以下是一些使用curl进行HTTP请求的常见用法和示例。 1. 发送…

DNS 域名解析

一 、名字解析介绍和DNS 当前TCP/IP网络中的设备之间进行通信&#xff0c;是利用和依赖于IP地址实现的。但数字形式的IP地址是很难记忆的。当网络设备众多&#xff0c;想要记住每个设备的IP地址&#xff0c;可以说是"不可能完成的任务"。那么如何解决这一难题呢&…

c语言线性方式初始化二维数组

线性方式初始化二维数组&#xff0c;只需要利用/与%的关系即可。具体细节文章下面会有程序的流程分析 问题起源 想要用线性方式初始化二维数组 问题分析 例如a[3][4] a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]如…

ssm基于Java的壁纸网站设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

java智慧校园小程序源码,家校互通小程序源码,智慧校园电子班牌云平台源码

智慧校园云平台电子班牌系统&#xff0c;利用先进的云计算技术&#xff0c;将教育信息化资源和教学管理系统进行有效整合&#xff0c;实现基础数据共享、应用统一管理。借助全新的智能交互识别终端和移动化教育管理系统&#xff0c;以考勤、课表、通知、家校互通等功能为切入点…

Head First Design Patterns -工厂模式

什么是工厂模式 工厂方法模式定义了一个创建对象的接口&#xff0c;但由子类来决定要实例化那个类。工厂方法让类把实例化推迟到了子类。 为什么要有工厂模式 书中以pizza店制作pizza为例子&#xff0c;假设不用工厂模式&#xff0c;在制作pizza阶段我们需要这样去实例化类&am…