【UEFI基础】EDK网络框架(ARP)

这篇具有很好参考价值的文章主要介绍了【UEFI基础】EDK网络框架(ARP)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ARP

ARP协议说明

从这里开始涉及到的网络协议都是比较通用的了,在一般的TCP/IP四层模型中都能够看到这些内容,不过这里主要介绍的还是其在BIOS下的实现,但是在此之前还是需要先说明ARP的作用。

ARP的全称是Address Resolution Protocol,它是一种解决地址问题的协议。以目标IP为线索,用来定义下一个应该接收数据分包的网络设备对应的MAC地址。ARP只用于IPv4,不能用于IPv6(IPv6使用ICMPv6替代ARP)。ARP获取MAC地址的简单流程如下:

【UEFI基础】EDK网络框架(ARP),UEFI开发基础,网络,uefi

ARP是请求方IP通过广播发送的请求包,同一链路上所有的主机和路由器都会接收到这个包,目标地址将自己的MAC地址填入到ARP响应包返回给请求方IP。一个ARP包的格式如下:

【UEFI基础】EDK网络框架(ARP),UEFI开发基础,网络,uefi

各个参数的说明如下:

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

  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中的主要数据以及它们的关系,如下图所示:

【UEFI基础】EDK网络框架(ARP),UEFI开发基础,网络,uefi

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),UEFI开发基础,网络,uefi文章来源地址https://www.toymoban.com/news/detail-808605.html

到了这里,关于【UEFI基础】EDK网络框架(ARP)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • UEFI基础——测试用例Hello Word

    硬件环境:龙芯ls3a6000平台 软件环境:龙芯uefi固件 GUID获取网址:https://guidgen.com mkdir TextPkg/ 三个文件 Hello.c 、 Hello.inf 、HelloPkg.dsc 1.1 Hello.c

    2024年02月07日
    浏览(33)
  • 【UEFI/BIOS】UEFI Shell Command (UEFI Shell 命令)篇目一

    命令 作用 alias 在UEFI Shell环境中显示、创建、删除别名 attrib 显示或更改文件或目录的属性 bcfg 操作Boot或者驱动程序顺序 cd 显示或更改当前目录 comp 以字节为单位比价两个文件的内容 connet 将驱动程序绑定到特定的设备并启动该驱动程序 cp 将一个或多个源文件或目录复制到目

    2024年02月07日
    浏览(32)
  • 【UEFI实战】UEFI图形显示(字符输出)

    接下来介绍 EFI_HII_FONT_PROTOCOL ,它在UEFI代码中完成了字符到像素的转换,本节主要介绍这个转换关系,它的实现代码在edk2MdeModulePkgUniversalHiiDatabaseDxeHiiDatabaseDxe.inf中,除了 EFI_HII_FONT_PROTOCOL ,这个模块还实现了很多其它的Protocol,后面用到的时候也会介绍,所以HiiDatabaseDx

    2024年02月13日
    浏览(28)
  • 《UEFI内核导读》UEFI Firmware Storage简介

     ============================== 敬请关注:“固件C字营     ==============================           UEFI固件一般存储在被称之为“固件仓库”的非易失性存储器中,简称为FD(固件设备),当前主流的存储介质是NorFlash它拥有非易失性、XIP以及可二次编程的特性。         固件设

    2023年04月08日
    浏览(27)
  • UEFI Shell命令详解,自写一个UEFI Shell命令

    命令 功能 命令 功能 acpiview 显示ACPI表相关信息 ifconfig 配置IP地址 alias 显示,创建,删除别名 load 加载UEFI驱动 attrib 显示,更改文件或目录属性 loadpcirom 加载PCI ROM bcfg 管理启动项 ls 列出目录内容或文件信息 cd 更改当前目录 map 显示Mapping cls 清空标准输出 memmap 显示目录映射

    2023年04月17日
    浏览(45)
  • uefi安全启动

    参考博客:UEFI安全启动 - 知乎 (zhihu.com) UEFI安全引导(Secure Boot)的核心职能就是利用数字签名来确认EFI驱动程序或者应用程序是否是受信任的。在简要地介绍了数字签名的概念(这是安全引导的基础)之后,我们重点介绍UEFI 安全引导是如何利用数字签名以及其他的加密方式

    2024年02月12日
    浏览(25)
  • 高通 UEFI:ABL(一)

    高通平台下的UEFI由XBL+ABL组成,主要完成各种客制化的需求实现,例如通过拉特定的gpio进入fastboot/recovery模式,读取ufs寿命,LCD兼容框架的实现等,想要实现客制化首先要搞明白源码种的框架组成,这篇文章先剖析一下abl阶段主要做了什么事情。 要分析abl框架,首先我们需要

    2024年02月09日
    浏览(33)
  • legacy启动和UEFI启动

    legacy启动是指传统的BIOS启动,和MBR分区模式相互依存,可以进行MBR分区系统的安装,支持所有Windows系统的安装,兼容性较强。Legacy作为传统的引导模式,Legacy模式支持磁盘分区为MBR结构,它能够引导32位系统也可以引导64位系统。 legacy启动意思是传统的BIOS启动,和MBR分区模

    2024年02月07日
    浏览(34)
  • UEFI统一可扩展固件接口

    统一可扩展固件接口(英语:Unified Extensible Firmware Interface,缩写UEFI)是一种个人电脑系统规格,用来定义操作系统与系统固件之间的软件界面,作为BIOS的替代方案。可扩展固件接口负责加电自检(POST)、联系操作系统以及提供连接操作系统与硬件的接口。 UEFI的前身是Int

    2024年02月04日
    浏览(26)
  • VM虚拟机 运行UEFI程序

    需要自行安装一个VM虚拟机,准备一个FAT32的U盘(U盘转格式时,最好用空U盘),U盘里面放你自己编译后生成的.efi文件。 1.新建虚拟机,点击“文件-》新建虚拟机” 大部分地方直接默认就行,这里只关注两个地方: 操作系统选Win10: 固件类型选择UEFI: 2.选中新建好的虚拟机

    2023年04月08日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包