/** @file | |
Functions implementation related with DHCPv4 for UefiPxeBc Driver. | |
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "PxeBcImpl.h" | |
// | |
// This is a map from the interested DHCP4 option tags' index to the tag value. | |
// | |
UINT8 mInterestedDhcp4Tags[PXEBC_DHCP4_TAG_INDEX_MAX] = { | |
DHCP4_TAG_BOOTFILE_LEN, | |
DHCP4_TAG_VENDOR, | |
DHCP4_TAG_OVERLOAD, | |
DHCP4_TAG_MSG_TYPE, | |
DHCP4_TAG_SERVER_ID, | |
DHCP4_TAG_VENDOR_CLASS_ID, | |
DHCP4_TAG_BOOTFILE | |
}; | |
// | |
// There are 4 times retries with the value of 4, 8, 16 and 32, refers to PXE2.1 spec. | |
// | |
UINT32 mPxeDhcpTimeout[4] = { 4, 8, 16, 32 }; | |
/** | |
Parse a certain dhcp4 option by OptTag in Buffer, and return with start pointer. | |
@param[in] Buffer Pointer to the option buffer. | |
@param[in] Length Length of the option buffer. | |
@param[in] OptTag Tag of the required option. | |
@retval NULL Failed to find the required option. | |
@retval Others The position of the required option. | |
**/ | |
EFI_DHCP4_PACKET_OPTION * | |
PxeBcParseDhcp4Options ( | |
IN UINT8 *Buffer, | |
IN UINT32 Length, | |
IN UINT8 OptTag | |
) | |
{ | |
EFI_DHCP4_PACKET_OPTION *Option; | |
UINT32 Offset; | |
Option = (EFI_DHCP4_PACKET_OPTION *)Buffer; | |
Offset = 0; | |
while (Offset < Length && Option->OpCode != DHCP4_TAG_EOP) { | |
if (Option->OpCode == OptTag) { | |
// | |
// Found the required option. | |
// | |
return Option; | |
} | |
// | |
// Skip the current option to the next. | |
// | |
if (Option->OpCode == DHCP4_TAG_PAD) { | |
Offset++; | |
} else { | |
Offset += Option->Length + 2; | |
} | |
Option = (EFI_DHCP4_PACKET_OPTION *)(Buffer + Offset); | |
} | |
return NULL; | |
} | |
/** | |
Parse the PXE vendor options and extract the information from them. | |
@param[in] Dhcp4Option Pointer to vendor options in buffer. | |
@param[in] VendorOption Pointer to structure to store information in vendor options. | |
**/ | |
VOID | |
PxeBcParseVendorOptions ( | |
IN EFI_DHCP4_PACKET_OPTION *Dhcp4Option, | |
IN PXEBC_VENDOR_OPTION *VendorOption | |
) | |
{ | |
UINT32 *BitMap; | |
UINT8 VendorOptionLen; | |
EFI_DHCP4_PACKET_OPTION *PxeOption; | |
UINT8 Offset; | |
BitMap = VendorOption->BitMap; | |
VendorOptionLen = Dhcp4Option->Length; | |
PxeOption = (EFI_DHCP4_PACKET_OPTION *)&Dhcp4Option->Data[0]; | |
Offset = 0; | |
ASSERT (PxeOption != NULL); | |
while ((Offset < VendorOptionLen) && (PxeOption->OpCode != DHCP4_TAG_EOP)) { | |
// | |
// Parse all the interesting PXE vendor options one by one. | |
// | |
switch (PxeOption->OpCode) { | |
case PXEBC_VENDOR_TAG_MTFTP_IP: | |
CopyMem (&VendorOption->MtftpIp, PxeOption->Data, sizeof (EFI_IPv4_ADDRESS)); | |
break; | |
case PXEBC_VENDOR_TAG_MTFTP_CPORT: | |
CopyMem (&VendorOption->MtftpCPort, PxeOption->Data, sizeof (VendorOption->MtftpCPort)); | |
break; | |
case PXEBC_VENDOR_TAG_MTFTP_SPORT: | |
CopyMem (&VendorOption->MtftpSPort, PxeOption->Data, sizeof (VendorOption->MtftpSPort)); | |
break; | |
case PXEBC_VENDOR_TAG_MTFTP_TIMEOUT: | |
VendorOption->MtftpTimeout = *PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_MTFTP_DELAY: | |
VendorOption->MtftpDelay = *PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_DISCOVER_CTRL: | |
VendorOption->DiscoverCtrl = *PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_DISCOVER_MCAST: | |
CopyMem (&VendorOption->DiscoverMcastIp, PxeOption->Data, sizeof (EFI_IPv4_ADDRESS)); | |
break; | |
case PXEBC_VENDOR_TAG_BOOT_SERVERS: | |
VendorOption->BootSvrLen = PxeOption->Length; | |
VendorOption->BootSvr = (PXEBC_BOOT_SVR_ENTRY *)PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_BOOT_MENU: | |
VendorOption->BootMenuLen = PxeOption->Length; | |
VendorOption->BootMenu = (PXEBC_BOOT_MENU_ENTRY *)PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_MENU_PROMPT: | |
VendorOption->MenuPromptLen = PxeOption->Length; | |
VendorOption->MenuPrompt = (PXEBC_MENU_PROMPT *)PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_MCAST_ALLOC: | |
CopyMem (&VendorOption->McastIpBase, PxeOption->Data, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&VendorOption->McastIpBlock, PxeOption->Data + 4, sizeof (VendorOption->McastIpBlock)); | |
CopyMem (&VendorOption->McastIpRange, PxeOption->Data + 6, sizeof (VendorOption->McastIpRange)); | |
break; | |
case PXEBC_VENDOR_TAG_CREDENTIAL_TYPES: | |
VendorOption->CredTypeLen = PxeOption->Length; | |
VendorOption->CredType = (UINT32 *)PxeOption->Data; | |
break; | |
case PXEBC_VENDOR_TAG_BOOT_ITEM: | |
CopyMem (&VendorOption->BootSrvType, PxeOption->Data, sizeof (VendorOption->BootSrvType)); | |
CopyMem (&VendorOption->BootSrvLayer, PxeOption->Data + 2, sizeof (VendorOption->BootSrvLayer)); | |
break; | |
default: | |
// | |
// Not interesting PXE vendor options. | |
// | |
break; | |
} | |
// | |
// Set the bit map for the special PXE options. | |
// | |
SET_VENDOR_OPTION_BIT_MAP (BitMap, PxeOption->OpCode); | |
// | |
// Continue to the next option. | |
// | |
if (PxeOption->OpCode == DHCP4_TAG_PAD) { | |
Offset++; | |
} else { | |
Offset = (UINT8)(Offset + PxeOption->Length + 2); | |
} | |
PxeOption = (EFI_DHCP4_PACKET_OPTION *)(Dhcp4Option->Data + Offset); | |
} | |
} | |
/** | |
Build the options buffer for the DHCPv4 request packet. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[out] OptList Pointer to the option pointer array. | |
@param[in] Buffer Pointer to the buffer to contain the option list. | |
@param[in] NeedMsgType If TRUE, it is necessary to include the Msg type option. | |
Otherwise, it is not necessary. | |
@return Index The count of the built-in options. | |
**/ | |
UINT32 | |
PxeBcBuildDhcp4Options ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
OUT EFI_DHCP4_PACKET_OPTION **OptList, | |
IN UINT8 *Buffer, | |
IN BOOLEAN NeedMsgType | |
) | |
{ | |
UINT32 Index; | |
PXEBC_DHCP4_OPTION_ENTRY OptEnt; | |
UINT16 Value; | |
Index = 0; | |
OptList[0] = (EFI_DHCP4_PACKET_OPTION *)Buffer; | |
if (NeedMsgType) { | |
// | |
// Append message type. | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_MSG_TYPE; | |
OptList[Index]->Length = 1; | |
OptEnt.Mesg = (PXEBC_DHCP4_OPTION_MESG *)OptList[Index]->Data; | |
OptEnt.Mesg->Type = PXEBC_DHCP4_MSG_TYPE_REQUEST; | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
// | |
// Append max message size. | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_MAXMSG; | |
OptList[Index]->Length = (UINT8)sizeof (PXEBC_DHCP4_OPTION_MAX_MESG_SIZE); | |
OptEnt.MaxMesgSize = (PXEBC_DHCP4_OPTION_MAX_MESG_SIZE *)OptList[Index]->Data; | |
Value = NTOHS (PXEBC_DHCP4_PACKET_MAX_SIZE); | |
CopyMem (&OptEnt.MaxMesgSize->Size, &Value, sizeof (UINT16)); | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
} | |
// | |
// Append parameter request list option. | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_PARA_LIST; | |
OptList[Index]->Length = 35; | |
OptEnt.Para = (PXEBC_DHCP4_OPTION_PARA *)OptList[Index]->Data; | |
OptEnt.Para->ParaList[0] = DHCP4_TAG_NETMASK; | |
OptEnt.Para->ParaList[1] = DHCP4_TAG_TIME_OFFSET; | |
OptEnt.Para->ParaList[2] = DHCP4_TAG_ROUTER; | |
OptEnt.Para->ParaList[3] = DHCP4_TAG_TIME_SERVER; | |
OptEnt.Para->ParaList[4] = DHCP4_TAG_NAME_SERVER; | |
OptEnt.Para->ParaList[5] = DHCP4_TAG_DNS_SERVER; | |
OptEnt.Para->ParaList[6] = DHCP4_TAG_HOSTNAME; | |
OptEnt.Para->ParaList[7] = DHCP4_TAG_BOOTFILE_LEN; | |
OptEnt.Para->ParaList[8] = DHCP4_TAG_DOMAINNAME; | |
OptEnt.Para->ParaList[9] = DHCP4_TAG_ROOTPATH; | |
OptEnt.Para->ParaList[10] = DHCP4_TAG_EXTEND_PATH; | |
OptEnt.Para->ParaList[11] = DHCP4_TAG_EMTU; | |
OptEnt.Para->ParaList[12] = DHCP4_TAG_TTL; | |
OptEnt.Para->ParaList[13] = DHCP4_TAG_BROADCAST; | |
OptEnt.Para->ParaList[14] = DHCP4_TAG_NIS_DOMAIN; | |
OptEnt.Para->ParaList[15] = DHCP4_TAG_NIS_SERVER; | |
OptEnt.Para->ParaList[16] = DHCP4_TAG_NTP_SERVER; | |
OptEnt.Para->ParaList[17] = DHCP4_TAG_VENDOR; | |
OptEnt.Para->ParaList[18] = DHCP4_TAG_REQUEST_IP; | |
OptEnt.Para->ParaList[19] = DHCP4_TAG_LEASE; | |
OptEnt.Para->ParaList[20] = DHCP4_TAG_SERVER_ID; | |
OptEnt.Para->ParaList[21] = DHCP4_TAG_T1; | |
OptEnt.Para->ParaList[22] = DHCP4_TAG_T2; | |
OptEnt.Para->ParaList[23] = DHCP4_TAG_VENDOR_CLASS_ID; | |
OptEnt.Para->ParaList[24] = DHCP4_TAG_TFTP; | |
OptEnt.Para->ParaList[25] = DHCP4_TAG_BOOTFILE; | |
OptEnt.Para->ParaList[26] = DHCP4_TAG_UUID; | |
OptEnt.Para->ParaList[27] = 0x80; | |
OptEnt.Para->ParaList[28] = 0x81; | |
OptEnt.Para->ParaList[29] = 0x82; | |
OptEnt.Para->ParaList[30] = 0x83; | |
OptEnt.Para->ParaList[31] = 0x84; | |
OptEnt.Para->ParaList[32] = 0x85; | |
OptEnt.Para->ParaList[33] = 0x86; | |
OptEnt.Para->ParaList[34] = 0x87; | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
// | |
// Append UUID/Guid-based client identifier option | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_UUID; | |
OptList[Index]->Length = (UINT8)sizeof (PXEBC_DHCP4_OPTION_UUID); | |
OptEnt.Uuid = (PXEBC_DHCP4_OPTION_UUID *)OptList[Index]->Data; | |
OptEnt.Uuid->Type = 0; | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
if (EFI_ERROR (NetLibGetSystemGuid ((EFI_GUID *)OptEnt.Uuid->Guid))) { | |
// | |
// Zero the Guid to indicate NOT programmable if failed to get system Guid. | |
// | |
DEBUG ((DEBUG_WARN, "PXE: Failed to read system GUID from the smbios table!\n")); | |
ZeroMem (OptEnt.Uuid->Guid, sizeof (EFI_GUID)); | |
} | |
// | |
// Append client network device interface option | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_UNDI; | |
OptList[Index]->Length = (UINT8)sizeof (PXEBC_DHCP4_OPTION_UNDI); | |
OptEnt.Undi = (PXEBC_DHCP4_OPTION_UNDI *)OptList[Index]->Data; | |
if (Private->Nii != NULL) { | |
OptEnt.Undi->Type = Private->Nii->Type; | |
OptEnt.Undi->MajorVer = Private->Nii->MajorVer; | |
OptEnt.Undi->MinorVer = Private->Nii->MinorVer; | |
} else { | |
OptEnt.Undi->Type = DEFAULT_UNDI_TYPE; | |
OptEnt.Undi->MajorVer = DEFAULT_UNDI_MAJOR; | |
OptEnt.Undi->MinorVer = DEFAULT_UNDI_MINOR; | |
} | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
// | |
// Append client system architecture option | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_ARCH; | |
OptList[Index]->Length = (UINT8)sizeof (PXEBC_DHCP4_OPTION_ARCH); | |
OptEnt.Arch = (PXEBC_DHCP4_OPTION_ARCH *)OptList[Index]->Data; | |
Value = HTONS (EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE); | |
CopyMem (&OptEnt.Arch->Type, &Value, sizeof (UINT16)); | |
Index++; | |
OptList[Index] = GET_NEXT_DHCP_OPTION (OptList[Index - 1]); | |
// | |
// Append vendor class identify option | |
// | |
OptList[Index]->OpCode = DHCP4_TAG_VENDOR_CLASS_ID; | |
OptList[Index]->Length = (UINT8)sizeof (PXEBC_DHCP4_OPTION_CLID); | |
OptEnt.Clid = (PXEBC_DHCP4_OPTION_CLID *)OptList[Index]->Data; | |
CopyMem ( | |
OptEnt.Clid, | |
DEFAULT_CLASS_ID_DATA, | |
sizeof (PXEBC_DHCP4_OPTION_CLID) | |
); | |
PxeBcUintnToAscDecWithFormat ( | |
EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE, | |
OptEnt.Clid->ArchitectureType, | |
sizeof (OptEnt.Clid->ArchitectureType) | |
); | |
if (Private->Nii != NULL) { | |
CopyMem (OptEnt.Clid->InterfaceName, Private->Nii->StringId, sizeof (OptEnt.Clid->InterfaceName)); | |
PxeBcUintnToAscDecWithFormat (Private->Nii->MajorVer, OptEnt.Clid->UndiMajor, sizeof (OptEnt.Clid->UndiMajor)); | |
PxeBcUintnToAscDecWithFormat (Private->Nii->MinorVer, OptEnt.Clid->UndiMinor, sizeof (OptEnt.Clid->UndiMinor)); | |
} | |
Index++; | |
return Index; | |
} | |
/** | |
Create a template DHCPv4 packet as a seed. | |
@param[out] Seed Pointer to the seed packet. | |
@param[in] Udp4 Pointer to EFI_UDP4_PROTOCOL. | |
**/ | |
VOID | |
PxeBcSeedDhcp4Packet ( | |
OUT EFI_DHCP4_PACKET *Seed, | |
IN EFI_UDP4_PROTOCOL *Udp4 | |
) | |
{ | |
EFI_SIMPLE_NETWORK_MODE Mode; | |
EFI_DHCP4_HEADER *Header; | |
// | |
// Get IfType and HwAddressSize from SNP mode data. | |
// | |
Udp4->GetModeData (Udp4, NULL, NULL, NULL, &Mode); | |
Seed->Size = sizeof (EFI_DHCP4_PACKET); | |
Seed->Length = sizeof (Seed->Dhcp4); | |
Header = &Seed->Dhcp4.Header; | |
ZeroMem (Header, sizeof (EFI_DHCP4_HEADER)); | |
Header->OpCode = PXEBC_DHCP4_OPCODE_REQUEST; | |
Header->HwType = Mode.IfType; | |
Header->HwAddrLen = (UINT8)Mode.HwAddressSize; | |
CopyMem (Header->ClientHwAddr, &Mode.CurrentAddress, Header->HwAddrLen); | |
Seed->Dhcp4.Magik = PXEBC_DHCP4_MAGIC; | |
Seed->Dhcp4.Option[0] = DHCP4_TAG_EOP; | |
} | |
/** | |
Cache the DHCPv4 packet. | |
@param[in] Dst Pointer to the cache buffer for DHCPv4 packet. | |
@param[in] Src Pointer to the DHCPv4 packet to be cached. | |
@retval EFI_SUCCESS Packet is copied. | |
@retval EFI_BUFFER_TOO_SMALL Cache buffer is not big enough to hold the packet. | |
**/ | |
EFI_STATUS | |
PxeBcCacheDhcp4Packet ( | |
IN EFI_DHCP4_PACKET *Dst, | |
IN EFI_DHCP4_PACKET *Src | |
) | |
{ | |
if (Dst->Size < Src->Length) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
CopyMem (&Dst->Dhcp4, &Src->Dhcp4, Src->Length); | |
Dst->Length = Src->Length; | |
return EFI_SUCCESS; | |
} | |
/** | |
Parse the cached DHCPv4 packet, including all the options. | |
@param[in] Cache4 Pointer to cached DHCPv4 packet. | |
@retval EFI_SUCCESS Parsed the DHCPv4 packet successfully. | |
@retval EFI_DEVICE_ERROR Failed to parse and invalid packet. | |
**/ | |
EFI_STATUS | |
PxeBcParseDhcp4Packet ( | |
IN PXEBC_DHCP4_PACKET_CACHE *Cache4 | |
) | |
{ | |
EFI_DHCP4_PACKET *Offer; | |
EFI_DHCP4_PACKET_OPTION **Options; | |
EFI_DHCP4_PACKET_OPTION *Option; | |
PXEBC_OFFER_TYPE OfferType; | |
UINTN Index; | |
BOOLEAN IsProxyOffer; | |
BOOLEAN IsPxeOffer; | |
UINT8 *Ptr8; | |
BOOLEAN FileFieldOverloaded; | |
IsProxyOffer = FALSE; | |
IsPxeOffer = FALSE; | |
FileFieldOverloaded = FALSE; | |
ZeroMem (Cache4->OptList, sizeof (Cache4->OptList)); | |
ZeroMem (&Cache4->VendorOpt, sizeof (Cache4->VendorOpt)); | |
Offer = &Cache4->Packet.Offer; | |
Options = Cache4->OptList; | |
// | |
// Parse DHCPv4 options in this offer, and store the pointers. | |
// First, try to parse DHCPv4 options from the DHCP optional parameters field. | |
// | |
for (Index = 0; Index < PXEBC_DHCP4_TAG_INDEX_MAX; Index++) { | |
Options[Index] = PxeBcParseDhcp4Options ( | |
Offer->Dhcp4.Option, | |
GET_OPTION_BUFFER_LEN (Offer), | |
mInterestedDhcp4Tags[Index] | |
); | |
} | |
// | |
// Second, Check if bootfilename and serverhostname is overloaded to carry DHCP options refers to rfc-2132. | |
// If yes, try to parse options from the BootFileName field, then ServerName field. | |
// | |
Option = Options[PXEBC_DHCP4_TAG_INDEX_OVERLOAD]; | |
if (Option != NULL) { | |
if ((Option->Data[0] & PXEBC_DHCP4_OVERLOAD_FILE) != 0) { | |
FileFieldOverloaded = TRUE; | |
for (Index = 0; Index < PXEBC_DHCP4_TAG_INDEX_MAX; Index++) { | |
if (Options[Index] == NULL) { | |
Options[Index] = PxeBcParseDhcp4Options ( | |
(UINT8 *)Offer->Dhcp4.Header.BootFileName, | |
sizeof (Offer->Dhcp4.Header.BootFileName), | |
mInterestedDhcp4Tags[Index] | |
); | |
} | |
} | |
} | |
if ((Option->Data[0] & PXEBC_DHCP4_OVERLOAD_SERVER_NAME) != 0) { | |
for (Index = 0; Index < PXEBC_DHCP4_TAG_INDEX_MAX; Index++) { | |
if (Options[Index] == NULL) { | |
Options[Index] = PxeBcParseDhcp4Options ( | |
(UINT8 *)Offer->Dhcp4.Header.ServerName, | |
sizeof (Offer->Dhcp4.Header.ServerName), | |
mInterestedDhcp4Tags[Index] | |
); | |
} | |
} | |
} | |
} | |
// | |
// The offer with zero "yiaddr" is a proxy offer. | |
// | |
if (Offer->Dhcp4.Header.YourAddr.Addr[0] == 0) { | |
IsProxyOffer = TRUE; | |
} | |
// | |
// The offer with "PXEClient" is a PXE offer. | |
// | |
Option = Options[PXEBC_DHCP4_TAG_INDEX_CLASS_ID]; | |
if ((Option != NULL) && (Option->Length >= 9) && | |
(CompareMem (Option->Data, DEFAULT_CLASS_ID_DATA, 9) == 0)) | |
{ | |
IsPxeOffer = TRUE; | |
} | |
// | |
// Parse PXE vendor options in this offer, and store the contents/pointers. | |
// | |
Option = Options[PXEBC_DHCP4_TAG_INDEX_VENDOR]; | |
if (IsPxeOffer && (Option != NULL)) { | |
PxeBcParseVendorOptions (Option, &Cache4->VendorOpt); | |
} | |
// | |
// Parse PXE boot file name: | |
// According to PXE spec, boot file name should be read from DHCP option 67 (bootfile name) if present. | |
// Otherwise, read from boot file field in DHCP header. | |
// | |
if (Options[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL) { | |
// | |
// RFC 2132, Section 9.5 does not strictly state Bootfile name (option 67) is null | |
// terminated string. So force to append null terminated character at the end of string. | |
// | |
Ptr8 = (UINT8 *)&Options[PXEBC_DHCP4_TAG_INDEX_BOOTFILE]->Data[0]; | |
Ptr8 += Options[PXEBC_DHCP4_TAG_INDEX_BOOTFILE]->Length; | |
if (*(Ptr8 - 1) != '\0') { | |
*Ptr8 = '\0'; | |
} | |
} else if (!FileFieldOverloaded && (Offer->Dhcp4.Header.BootFileName[0] != 0)) { | |
// | |
// If the bootfile is not present and bootfilename is present in DHCPv4 packet, just parse it. | |
// Do not count dhcp option header here, or else will destroy the serverhostname. | |
// | |
Options[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] = (EFI_DHCP4_PACKET_OPTION *) | |
(&Offer->Dhcp4.Header.BootFileName[0] - | |
OFFSET_OF (EFI_DHCP4_PACKET_OPTION, Data[0])); | |
} | |
// | |
// Determine offer type of the DHCPv4 packet. | |
// | |
Option = Options[PXEBC_DHCP4_TAG_INDEX_MSG_TYPE]; | |
if ((Option == NULL) || (Option->Data[0] == 0)) { | |
// | |
// It's a Bootp offer. | |
// | |
OfferType = PxeOfferTypeBootp; | |
Option = Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE]; | |
if (Option == NULL) { | |
// | |
// If the Bootp offer without bootfilename, discard it. | |
// | |
return EFI_DEVICE_ERROR; | |
} | |
} else { | |
if (IS_VALID_DISCOVER_VENDOR_OPTION (Cache4->VendorOpt.BitMap)) { | |
// | |
// It's a PXE10 offer with PXEClient and discover vendor option. | |
// | |
OfferType = IsProxyOffer ? PxeOfferTypeProxyPxe10 : PxeOfferTypeDhcpPxe10; | |
} else if (IS_VALID_MTFTP_VENDOR_OPTION (Cache4->VendorOpt.BitMap)) { | |
// | |
// It's a WFM11a offer with PXEClient and mtftp vendor option. | |
// But multi-cast download is not supported currently, so discard it. | |
// | |
return EFI_DEVICE_ERROR; | |
} else if (IsPxeOffer) { | |
// | |
// It's a BINL offer only with PXEClient. | |
// | |
OfferType = IsProxyOffer ? PxeOfferTypeProxyBinl : PxeOfferTypeDhcpBinl; | |
} else { | |
// | |
// It's a DHCPv4 only offer, which is a pure DHCPv4 offer packet. | |
// | |
OfferType = PxeOfferTypeDhcpOnly; | |
} | |
} | |
Cache4->OfferType = OfferType; | |
return EFI_SUCCESS; | |
} | |
/** | |
Cache the DHCPv4 ack packet, and parse it on demand. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] Ack Pointer to the DHCPv4 ack packet. | |
@param[in] Verified If TRUE, parse the ACK packet and store info into mode data. | |
@retval EFI_SUCCESS Cache and parse the packet successfully. | |
@retval EFI_BUFFER_TOO_SMALL Cache buffer is not big enough to hold the packet. | |
**/ | |
EFI_STATUS | |
PxeBcCopyDhcp4Ack ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN EFI_DHCP4_PACKET *Ack, | |
IN BOOLEAN Verified | |
) | |
{ | |
EFI_PXE_BASE_CODE_MODE *Mode; | |
EFI_STATUS Status; | |
Mode = Private->PxeBc.Mode; | |
Status = PxeBcCacheDhcp4Packet (&Private->DhcpAck.Dhcp4.Packet.Ack, Ack); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (Verified) { | |
// | |
// Parse the ack packet and store it into mode data if needed. | |
// | |
PxeBcParseDhcp4Packet (&Private->DhcpAck.Dhcp4); | |
CopyMem (&Mode->DhcpAck.Dhcpv4, &Ack->Dhcp4, Ack->Length); | |
Mode->DhcpAckReceived = TRUE; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Cache the DHCPv4 proxy offer packet according to the received order. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] OfferIndex The received order of offer packets. | |
@retval EFI_SUCCESS Cache and parse the packet successfully. | |
@retval EFI_BUFFER_TOO_SMALL Cache buffer is not big enough to hold the packet. | |
**/ | |
EFI_STATUS | |
PxeBcCopyProxyOffer ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN UINT32 OfferIndex | |
) | |
{ | |
EFI_PXE_BASE_CODE_MODE *Mode; | |
EFI_DHCP4_PACKET *Offer; | |
EFI_STATUS Status; | |
ASSERT (OfferIndex < Private->OfferNum); | |
ASSERT (OfferIndex < PXEBC_OFFER_MAX_NUM); | |
Mode = Private->PxeBc.Mode; | |
Offer = &Private->OfferBuffer[OfferIndex].Dhcp4.Packet.Offer; | |
// | |
// Cache the proxy offer packet and parse it. | |
// | |
Status = PxeBcCacheDhcp4Packet (&Private->ProxyOffer.Dhcp4.Packet.Offer, Offer); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
PxeBcParseDhcp4Packet (&Private->ProxyOffer.Dhcp4); | |
// | |
// Store this packet into mode data. | |
// | |
CopyMem (&Mode->ProxyOffer.Dhcpv4, &Offer->Dhcp4, Offer->Length); | |
Mode->ProxyOfferReceived = TRUE; | |
return EFI_SUCCESS; | |
} | |
/** | |
Retry to request bootfile name by the BINL offer. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] Index The received order of offer packets. | |
@retval EFI_SUCCESS Successfully retried to request bootfile name. | |
@retval EFI_DEVICE_ERROR Failed to retry bootfile name. | |
**/ | |
EFI_STATUS | |
PxeBcRetryBinlOffer ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN UINT32 Index | |
) | |
{ | |
EFI_DHCP4_PACKET *Offer; | |
EFI_IP_ADDRESS ServerIp; | |
EFI_STATUS Status; | |
PXEBC_DHCP4_PACKET_CACHE *Cache4; | |
EFI_DHCP4_PACKET *Reply; | |
ASSERT (Index < PXEBC_OFFER_MAX_NUM); | |
ASSERT ( | |
Private->OfferBuffer[Index].Dhcp4.OfferType == PxeOfferTypeDhcpBinl || | |
Private->OfferBuffer[Index].Dhcp4.OfferType == PxeOfferTypeProxyBinl | |
); | |
Offer = &Private->OfferBuffer[Index].Dhcp4.Packet.Offer; | |
// | |
// Prefer to siaddr in header as next server address. If it's zero, then use option 54. | |
// | |
if (Offer->Dhcp4.Header.ServerAddr.Addr[0] == 0) { | |
CopyMem ( | |
&ServerIp.Addr[0], | |
Private->OfferBuffer[Index].Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_SERVER_ID]->Data, | |
sizeof (EFI_IPv4_ADDRESS) | |
); | |
} else { | |
CopyMem ( | |
&ServerIp.Addr[0], | |
&Offer->Dhcp4.Header.ServerAddr, | |
sizeof (EFI_IPv4_ADDRESS) | |
); | |
} | |
Private->IsDoDiscover = FALSE; | |
Cache4 = &Private->ProxyOffer.Dhcp4; | |
Reply = &Cache4->Packet.Offer; | |
// | |
// Send another request packet for bootfile name. | |
// | |
Status = PxeBcDhcp4Discover ( | |
Private, | |
0, | |
NULL, | |
FALSE, | |
&ServerIp, | |
0, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Parse the reply for the last request packet. | |
// | |
Status = PxeBcParseDhcp4Packet (Cache4); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((Cache4->OfferType != PxeOfferTypeProxyPxe10) && | |
(Cache4->OfferType != PxeOfferTypeProxyWfm11a) && | |
(Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] == NULL)) | |
{ | |
// | |
// This BINL ack doesn't have discovery option set or multicast option set | |
// or bootfile name specified. | |
// | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// Store the reply into mode data. | |
// | |
Private->PxeBc.Mode->ProxyOfferReceived = TRUE; | |
CopyMem (&Private->PxeBc.Mode->ProxyOffer.Dhcpv4, &Reply->Dhcp4, Reply->Length); | |
return EFI_SUCCESS; | |
} | |
/** | |
Cache all the received DHCPv4 offers, and set OfferIndex and OfferCount. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] RcvdOffer Pointer to the received offer packet. | |
@retval EFI_SUCCESS Cache and parse the packet successfully. | |
@retval Others Operation failed. | |
**/ | |
EFI_STATUS | |
PxeBcCacheDhcp4Offer ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN EFI_DHCP4_PACKET *RcvdOffer | |
) | |
{ | |
PXEBC_DHCP4_PACKET_CACHE *Cache4; | |
EFI_DHCP4_PACKET *Offer; | |
PXEBC_OFFER_TYPE OfferType; | |
EFI_STATUS Status; | |
ASSERT (Private->OfferNum < PXEBC_OFFER_MAX_NUM); | |
Cache4 = &Private->OfferBuffer[Private->OfferNum].Dhcp4; | |
Offer = &Cache4->Packet.Offer; | |
// | |
// Cache the content of DHCPv4 packet firstly. | |
// | |
Status = PxeBcCacheDhcp4Packet (Offer, RcvdOffer); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Validate the DHCPv4 packet, and parse the options and offer type. | |
// | |
if (EFI_ERROR (PxeBcParseDhcp4Packet (Cache4))) { | |
return EFI_ABORTED; | |
} | |
// | |
// Determine whether cache the current offer by type, and record OfferIndex and OfferCount. | |
// | |
OfferType = Cache4->OfferType; | |
ASSERT (OfferType < PxeOfferTypeMax); | |
if (OfferType == PxeOfferTypeBootp) { | |
// | |
// It's a Bootp offer, only cache the first one, and discard the others. | |
// | |
if (Private->OfferCount[OfferType] == 0) { | |
Private->OfferIndex[OfferType][0] = Private->OfferNum; | |
Private->OfferCount[OfferType] = 1; | |
} else { | |
return EFI_ABORTED; | |
} | |
} else { | |
ASSERT (Private->OfferCount[OfferType] < PXEBC_OFFER_MAX_NUM); | |
if (IS_PROXY_DHCP_OFFER (Offer)) { | |
// | |
// It's a proxy offer without yiaddr, including PXE10, WFM11a or BINL offer. | |
// | |
Private->IsProxyRecved = TRUE; | |
if (OfferType == PxeOfferTypeProxyBinl) { | |
// | |
// Cache all proxy BINL offers. | |
// | |
Private->OfferIndex[OfferType][Private->OfferCount[OfferType]] = Private->OfferNum; | |
Private->OfferCount[OfferType]++; | |
} else if (((OfferType == PxeOfferTypeProxyPxe10) || (OfferType == PxeOfferTypeProxyWfm11a)) && | |
(Private->OfferCount[OfferType] < 1)) | |
{ | |
// | |
// Only cache the first PXE10/WFM11a offer, and discard the others. | |
// | |
Private->OfferIndex[OfferType][0] = Private->OfferNum; | |
Private->OfferCount[OfferType] = 1; | |
} else { | |
return EFI_ABORTED; | |
} | |
} else { | |
// | |
// It's a DHCPv4 offer with yiaddr, and cache them all. | |
// | |
Private->OfferIndex[OfferType][Private->OfferCount[OfferType]] = Private->OfferNum; | |
Private->OfferCount[OfferType]++; | |
} | |
} | |
Private->OfferNum++; | |
return EFI_SUCCESS; | |
} | |
/** | |
Select an DHCPv4 offer, and record SelectIndex and SelectProxyType. | |
@param[in] Private Pointer to PxeBc private data. | |
**/ | |
VOID | |
PxeBcSelectDhcp4Offer ( | |
IN PXEBC_PRIVATE_DATA *Private | |
) | |
{ | |
UINT32 Index; | |
UINT32 OfferIndex; | |
EFI_DHCP4_PACKET *Offer; | |
Private->SelectIndex = 0; | |
if (Private->IsOfferSorted) { | |
// | |
// Select offer by default policy. | |
// | |
if (Private->OfferCount[PxeOfferTypeDhcpPxe10] > 0) { | |
// | |
// 1. DhcpPxe10 offer | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpPxe10][0] + 1; | |
} else if (Private->OfferCount[PxeOfferTypeDhcpWfm11a] > 0) { | |
// | |
// 2. DhcpWfm11a offer | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpWfm11a][0] + 1; | |
} else if ((Private->OfferCount[PxeOfferTypeDhcpOnly] > 0) && | |
(Private->OfferCount[PxeOfferTypeProxyPxe10] > 0)) | |
{ | |
// | |
// 3. DhcpOnly offer and ProxyPxe10 offer. | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1; | |
Private->SelectProxyType = PxeOfferTypeProxyPxe10; | |
} else if ((Private->OfferCount[PxeOfferTypeDhcpOnly] > 0) && | |
(Private->OfferCount[PxeOfferTypeProxyWfm11a] > 0)) | |
{ | |
// | |
// 4. DhcpOnly offer and ProxyWfm11a offer. | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1; | |
Private->SelectProxyType = PxeOfferTypeProxyWfm11a; | |
} else if (Private->OfferCount[PxeOfferTypeDhcpBinl] > 0) { | |
// | |
// 5. DhcpBinl offer. | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpBinl][0] + 1; | |
} else if ((Private->OfferCount[PxeOfferTypeDhcpOnly] > 0) && | |
(Private->OfferCount[PxeOfferTypeProxyBinl] > 0)) | |
{ | |
// | |
// 6. DhcpOnly offer and ProxyBinl offer. | |
// | |
Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1; | |
Private->SelectProxyType = PxeOfferTypeProxyBinl; | |
} else { | |
// | |
// 7. DhcpOnly offer with bootfilename. | |
// | |
for (Index = 0; Index < Private->OfferCount[PxeOfferTypeDhcpOnly]; Index++) { | |
OfferIndex = Private->OfferIndex[PxeOfferTypeDhcpOnly][Index]; | |
if (Private->OfferBuffer[OfferIndex].Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL) { | |
Private->SelectIndex = OfferIndex + 1; | |
break; | |
} | |
} | |
// | |
// 8. Bootp offer with bootfilename. | |
// | |
OfferIndex = Private->OfferIndex[PxeOfferTypeBootp][0]; | |
if ((Private->SelectIndex == 0) && | |
(Private->OfferCount[PxeOfferTypeBootp] > 0) && | |
(Private->OfferBuffer[OfferIndex].Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL)) | |
{ | |
Private->SelectIndex = OfferIndex + 1; | |
} | |
} | |
} else { | |
// | |
// Select offer by received order. | |
// | |
for (Index = 0; Index < Private->OfferNum; Index++) { | |
Offer = &Private->OfferBuffer[Index].Dhcp4.Packet.Offer; | |
if (IS_PROXY_DHCP_OFFER (Offer)) { | |
// | |
// Skip proxy offers | |
// | |
continue; | |
} | |
if (!Private->IsProxyRecved && | |
(Private->OfferBuffer[Index].Dhcp4.OfferType == PxeOfferTypeDhcpOnly) && | |
(Private->OfferBuffer[Index].Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] == NULL)) | |
{ | |
// | |
// Skip if DhcpOnly offer without any other proxy offers or bootfilename. | |
// | |
continue; | |
} | |
// | |
// Record the index of the select offer. | |
// | |
Private->SelectIndex = Index + 1; | |
break; | |
} | |
} | |
} | |
/** | |
Handle the DHCPv4 offer packet. | |
@param[in] Private Pointer to PxeBc private data. | |
@retval EFI_SUCCESS Handled the DHCPv4 offer packet successfully. | |
@retval EFI_NO_RESPONSE No response to the following request packet. | |
@retval EFI_NOT_FOUND No boot filename received. | |
@retval EFI_BUFFER_TOO_SMALL Can't cache the offer pacet. | |
**/ | |
EFI_STATUS | |
PxeBcHandleDhcp4Offer ( | |
IN PXEBC_PRIVATE_DATA *Private | |
) | |
{ | |
PXEBC_DHCP4_PACKET_CACHE *Cache4; | |
EFI_DHCP4_PACKET_OPTION **Options; | |
UINT32 Index; | |
EFI_DHCP4_PACKET *Offer; | |
PXEBC_OFFER_TYPE OfferType; | |
UINT32 ProxyIndex; | |
UINT32 SelectIndex; | |
EFI_STATUS Status; | |
EFI_PXE_BASE_CODE_MODE *Mode; | |
EFI_DHCP4_PACKET *Ack; | |
ASSERT (Private->SelectIndex > 0); | |
SelectIndex = (UINT32)(Private->SelectIndex - 1); | |
ASSERT (SelectIndex < PXEBC_OFFER_MAX_NUM); | |
Cache4 = &Private->OfferBuffer[SelectIndex].Dhcp4; | |
Options = Cache4->OptList; | |
Status = EFI_SUCCESS; | |
if (Cache4->OfferType == PxeOfferTypeDhcpBinl) { | |
// | |
// DhcpBinl offer is selected, so need try to request bootfilename by this offer. | |
// | |
if (EFI_ERROR (PxeBcRetryBinlOffer (Private, SelectIndex))) { | |
Status = EFI_NO_RESPONSE; | |
} | |
} else if (Cache4->OfferType == PxeOfferTypeDhcpOnly) { | |
if (Private->IsProxyRecved) { | |
// | |
// DhcpOnly offer is selected, so need try to request bootfile name. | |
// | |
ProxyIndex = 0; | |
if (Private->IsOfferSorted) { | |
// | |
// The proxy offer should be determined if select by default policy. | |
// IsOfferSorted means all offers are labeled by OfferIndex. | |
// | |
ASSERT (Private->SelectProxyType < PxeOfferTypeMax); | |
ASSERT (Private->OfferCount[Private->SelectProxyType] > 0); | |
if (Private->SelectProxyType == PxeOfferTypeProxyBinl) { | |
// | |
// Try all the cached ProxyBinl offer one by one to request bootfile name. | |
// | |
for (Index = 0; Index < Private->OfferCount[Private->SelectProxyType]; Index++) { | |
ASSERT (Index < PXEBC_OFFER_MAX_NUM); | |
ProxyIndex = Private->OfferIndex[Private->SelectProxyType][Index]; | |
if (!EFI_ERROR (PxeBcRetryBinlOffer (Private, ProxyIndex))) { | |
break; | |
} | |
} | |
if (Index == Private->OfferCount[Private->SelectProxyType]) { | |
Status = EFI_NO_RESPONSE; | |
} | |
} else { | |
// | |
// For other proxy offers, only one is buffered. | |
// | |
ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0]; | |
} | |
} else { | |
// | |
// The proxy offer should not be determined if select by received order. | |
// | |
Status = EFI_NO_RESPONSE; | |
for (Index = 0; Index < Private->OfferNum; Index++) { | |
ASSERT (Index < PXEBC_OFFER_MAX_NUM); | |
Offer = &Private->OfferBuffer[Index].Dhcp4.Packet.Offer; | |
OfferType = Private->OfferBuffer[Index].Dhcp4.OfferType; | |
if (!IS_PROXY_DHCP_OFFER (Offer)) { | |
// | |
// Skip non proxy DHCPv4 offers. | |
// | |
continue; | |
} | |
if (OfferType == PxeOfferTypeProxyBinl) { | |
// | |
// Try all the cached ProxyBinl offer one by one to request bootfile name. | |
// | |
if (EFI_ERROR (PxeBcRetryBinlOffer (Private, Index))) { | |
continue; | |
} | |
} | |
Private->SelectProxyType = OfferType; | |
ProxyIndex = Index; | |
Status = EFI_SUCCESS; | |
break; | |
} | |
} | |
if (!EFI_ERROR (Status) && (Private->SelectProxyType != PxeOfferTypeProxyBinl)) { | |
// | |
// Success to try to request by a ProxyPxe10 or ProxyWfm11a offer, copy and parse it. | |
// | |
Status = PxeBcCopyProxyOffer (Private, ProxyIndex); | |
} | |
} else { | |
// | |
// Otherwise, the bootfile name must be included in DhcpOnly offer. | |
// | |
if (Options[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] == NULL) { | |
Status = EFI_NOT_FOUND; | |
} | |
} | |
} | |
if (!EFI_ERROR (Status)) { | |
// | |
// All PXE boot information is ready by now. | |
// | |
Mode = Private->PxeBc.Mode; | |
Offer = &Cache4->Packet.Offer; | |
Ack = &Private->DhcpAck.Dhcp4.Packet.Ack; | |
if (Cache4->OfferType == PxeOfferTypeBootp) { | |
// | |
// Bootp is a special case that only 2 packets involved instead of 4. So the bootp's reply | |
// should be taken as ack. | |
// | |
Ack = Offer; | |
} | |
Status = PxeBcCopyDhcp4Ack (Private, Ack, TRUE); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Mode->DhcpDiscoverValid = TRUE; | |
} | |
return Status; | |
} | |
/** | |
EFI_DHCP4_CALLBACK is provided by the consumer of the EFI DHCPv4 Protocol driver | |
to intercept events that occurred in the configuration process. | |
@param[in] This Pointer to the EFI DHCPv4 Protocol. | |
@param[in] Context Pointer to the context set by EFI_DHCP4_PROTOCOL.Configure(). | |
@param[in] CurrentState The current operational state of the EFI DHCPv4 Protocol driver. | |
@param[in] Dhcp4Event The event that occurs in the current state, which usually means a | |
state transition. | |
@param[in] Packet The DHCPv4 packet that is going to be sent or already received. | |
@param[out] NewPacket The packet that is used to replace the above Packet. | |
@retval EFI_SUCCESS Tells the EFI DHCPv4 Protocol driver to continue the DHCP process. | |
@retval EFI_NOT_READY Only used in the Dhcp4Selecting state. The EFI DHCPv4 Protocol | |
driver will continue to wait for more DHCPOFFER packets until the | |
retry timeout expires. | |
@retval EFI_ABORTED Tells the EFI DHCPv4 Protocol driver to abort the current process | |
and return to the Dhcp4Init or Dhcp4InitReboot state. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PxeBcDhcp4CallBack ( | |
IN EFI_DHCP4_PROTOCOL *This, | |
IN VOID *Context, | |
IN EFI_DHCP4_STATE CurrentState, | |
IN EFI_DHCP4_EVENT Dhcp4Event, | |
IN EFI_DHCP4_PACKET *Packet OPTIONAL, | |
OUT EFI_DHCP4_PACKET **NewPacket OPTIONAL | |
) | |
{ | |
PXEBC_PRIVATE_DATA *Private; | |
EFI_PXE_BASE_CODE_MODE *Mode; | |
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL *Callback; | |
EFI_DHCP4_PACKET_OPTION *MaxMsgSize; | |
UINT16 Value; | |
EFI_STATUS Status; | |
BOOLEAN Received; | |
if ((Dhcp4Event != Dhcp4RcvdOffer) && | |
(Dhcp4Event != Dhcp4SelectOffer) && | |
(Dhcp4Event != Dhcp4SendDiscover) && | |
(Dhcp4Event != Dhcp4RcvdAck)) | |
{ | |
return EFI_SUCCESS; | |
} | |
ASSERT (Packet != NULL); | |
Private = (PXEBC_PRIVATE_DATA *)Context; | |
Mode = Private->PxeBc.Mode; | |
Callback = Private->PxeBcCallback; | |
// | |
// Override the Maximum DHCP Message Size. | |
// | |
MaxMsgSize = PxeBcParseDhcp4Options ( | |
Packet->Dhcp4.Option, | |
GET_OPTION_BUFFER_LEN (Packet), | |
DHCP4_TAG_MAXMSG | |
); | |
if (MaxMsgSize != NULL) { | |
Value = HTONS (PXEBC_DHCP4_PACKET_MAX_SIZE); | |
CopyMem (MaxMsgSize->Data, &Value, sizeof (Value)); | |
} | |
// | |
// Callback to user if any packets sent or received. | |
// | |
if ((Dhcp4Event != Dhcp4SelectOffer) && (Callback != NULL)) { | |
Received = (BOOLEAN)(Dhcp4Event == Dhcp4RcvdOffer || Dhcp4Event == Dhcp4RcvdAck); | |
Status = Callback->Callback ( | |
Callback, | |
Private->Function, | |
Received, | |
Packet->Length, | |
(EFI_PXE_BASE_CODE_PACKET *)&Packet->Dhcp4 | |
); | |
if (Status != EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE) { | |
return EFI_ABORTED; | |
} | |
} | |
Status = EFI_SUCCESS; | |
switch (Dhcp4Event) { | |
case Dhcp4SendDiscover: | |
if (Packet->Length > PXEBC_DHCP4_PACKET_MAX_SIZE) { | |
// | |
// If the to be sent packet exceeds the maximum length, abort the DHCP process. | |
// | |
Status = EFI_ABORTED; | |
break; | |
} | |
// | |
// Cache the DHCPv4 discover packet to mode data directly. | |
// It need to check SendGuid as well as Dhcp4SendRequest. | |
// | |
CopyMem (&Mode->DhcpDiscover.Dhcpv4, &Packet->Dhcp4, Packet->Length); | |
case Dhcp4SendRequest: | |
if (Packet->Length > PXEBC_DHCP4_PACKET_MAX_SIZE) { | |
// | |
// If the to be sent packet exceeds the maximum length, abort the DHCP process. | |
// | |
Status = EFI_ABORTED; | |
break; | |
} | |
if (Mode->SendGUID) { | |
// | |
// Send the system Guid instead of the MAC address as the hardware address if required. | |
// | |
if (EFI_ERROR (NetLibGetSystemGuid ((EFI_GUID *)Packet->Dhcp4.Header.ClientHwAddr))) { | |
// | |
// Zero the Guid to indicate NOT programmable if failed to get system Guid. | |
// | |
DEBUG ((DEBUG_WARN, "PXE: Failed to read system GUID from the smbios table!\n")); | |
ZeroMem (Packet->Dhcp4.Header.ClientHwAddr, sizeof (EFI_GUID)); | |
} | |
Packet->Dhcp4.Header.HwAddrLen = (UINT8)sizeof (EFI_GUID); | |
} | |
break; | |
case Dhcp4RcvdOffer: | |
Status = EFI_NOT_READY; | |
if (Packet->Length > PXEBC_DHCP4_PACKET_MAX_SIZE) { | |
// | |
// Ignore the incoming packets which exceed the maximum length. | |
// | |
break; | |
} | |
if (Private->OfferNum < PXEBC_OFFER_MAX_NUM) { | |
// | |
// Cache the DHCPv4 offers to OfferBuffer[] for select later, and record | |
// the OfferIndex and OfferCount. | |
// If error happens, just ignore this packet and continue to wait more offer. | |
// | |
PxeBcCacheDhcp4Offer (Private, Packet); | |
} | |
break; | |
case Dhcp4SelectOffer: | |
ASSERT (NewPacket != NULL); | |
// | |
// Select offer by the default policy or by order, and record the SelectIndex | |
// and SelectProxyType. | |
// | |
PxeBcSelectDhcp4Offer (Private); | |
if (Private->SelectIndex == 0) { | |
Status = EFI_ABORTED; | |
} else { | |
*NewPacket = &Private->OfferBuffer[Private->SelectIndex - 1].Dhcp4.Packet.Offer; | |
} | |
break; | |
case Dhcp4RcvdAck: | |
// | |
// Cache the DHCPv4 ack to Private->Dhcp4Ack, but it's not the final ack in mode data | |
// without verification. | |
// | |
ASSERT (Private->SelectIndex != 0); | |
Status = PxeBcCopyDhcp4Ack (Private, Packet, FALSE); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_ABORTED; | |
} | |
break; | |
default: | |
break; | |
} | |
return Status; | |
} | |
/** | |
Build and send out the request packet for the bootfile, and parse the reply. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] Type PxeBc option boot item type. | |
@param[in] Layer Pointer to option boot item layer. | |
@param[in] UseBis Use BIS or not. | |
@param[in] DestIp Pointer to the server address. | |
@param[in] IpCount The total count of the server address. | |
@param[in] SrvList Pointer to EFI_PXE_BASE_CODE_SRVLIST. | |
@retval EFI_SUCCESS Successfully discovered boot file. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate resource. | |
@retval EFI_NOT_FOUND Can't get the PXE reply packet. | |
@retval Others Failed to discover boot file. | |
**/ | |
EFI_STATUS | |
PxeBcDhcp4Discover ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN UINT16 Type, | |
IN UINT16 *Layer, | |
IN BOOLEAN UseBis, | |
IN EFI_IP_ADDRESS *DestIp, | |
IN UINT16 IpCount, | |
IN EFI_PXE_BASE_CODE_SRVLIST *SrvList | |
) | |
{ | |
EFI_PXE_BASE_CODE_UDP_PORT Sport; | |
EFI_PXE_BASE_CODE_MODE *Mode; | |
EFI_DHCP4_PROTOCOL *Dhcp4; | |
EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN Token; | |
BOOLEAN IsBCast; | |
EFI_STATUS Status; | |
UINT16 RepIndex; | |
UINT16 SrvIndex; | |
UINT16 TryIndex; | |
EFI_DHCP4_LISTEN_POINT ListenPoint; | |
EFI_DHCP4_PACKET *Response; | |
UINT8 Buffer[PXEBC_DHCP4_OPTION_MAX_SIZE]; | |
EFI_DHCP4_PACKET_OPTION *OptList[PXEBC_DHCP4_OPTION_MAX_NUM]; | |
UINT32 OptCount; | |
EFI_DHCP4_PACKET_OPTION *PxeOpt; | |
PXEBC_OPTION_BOOT_ITEM *PxeBootItem; | |
UINT8 VendorOptLen; | |
UINT32 Xid; | |
Mode = Private->PxeBc.Mode; | |
Dhcp4 = Private->Dhcp4; | |
Status = EFI_SUCCESS; | |
ZeroMem (&Token, sizeof (EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN)); | |
// | |
// Use broadcast if destination address not specified. | |
// | |
if (DestIp == NULL) { | |
Sport = PXEBC_DHCP4_S_PORT; | |
IsBCast = TRUE; | |
} else { | |
Sport = PXEBC_BS_DISCOVER_PORT; | |
IsBCast = FALSE; | |
} | |
if (!UseBis && (Layer != NULL)) { | |
*Layer &= EFI_PXE_BASE_CODE_BOOT_LAYER_MASK; | |
} | |
// | |
// Build all the options for the request packet. | |
// | |
OptCount = PxeBcBuildDhcp4Options (Private, OptList, Buffer, TRUE); | |
if (Private->IsDoDiscover) { | |
// | |
// Add vendor option of PXE_BOOT_ITEM | |
// | |
VendorOptLen = (UINT8)((sizeof (EFI_DHCP4_PACKET_OPTION) - 1) * 2 + sizeof (PXEBC_OPTION_BOOT_ITEM) + 1); | |
OptList[OptCount] = AllocateZeroPool (VendorOptLen); | |
if (OptList[OptCount] == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
OptList[OptCount]->OpCode = DHCP4_TAG_VENDOR; | |
OptList[OptCount]->Length = (UINT8)(VendorOptLen - 2); | |
PxeOpt = (EFI_DHCP4_PACKET_OPTION *)OptList[OptCount]->Data; | |
PxeOpt->OpCode = PXEBC_VENDOR_TAG_BOOT_ITEM; | |
PxeOpt->Length = (UINT8)sizeof (PXEBC_OPTION_BOOT_ITEM); | |
PxeBootItem = (PXEBC_OPTION_BOOT_ITEM *)PxeOpt->Data; | |
PxeBootItem->Type = HTONS (Type); | |
PxeOpt->Data[PxeOpt->Length] = DHCP4_TAG_EOP; | |
if (Layer != NULL) { | |
PxeBootItem->Layer = HTONS (*Layer); | |
} | |
OptCount++; | |
} | |
// | |
// Build the request packet with seed packet and option list. | |
// | |
Status = Dhcp4->Build ( | |
Dhcp4, | |
&Private->SeedPacket, | |
0, | |
NULL, | |
OptCount, | |
OptList, | |
&Token.Packet | |
); | |
// | |
// Free the vendor option of PXE_BOOT_ITEM. | |
// | |
if (Private->IsDoDiscover) { | |
FreePool (OptList[OptCount - 1]); | |
} | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (Mode->SendGUID) { | |
if (EFI_ERROR (NetLibGetSystemGuid ((EFI_GUID *)Token.Packet->Dhcp4.Header.ClientHwAddr))) { | |
// | |
// Zero the Guid to indicate NOT programmable if failed to get system Guid. | |
// | |
DEBUG ((DEBUG_WARN, "PXE: Failed to read system GUID from the smbios table!\n")); | |
ZeroMem (Token.Packet->Dhcp4.Header.ClientHwAddr, sizeof (EFI_GUID)); | |
} | |
Token.Packet->Dhcp4.Header.HwAddrLen = (UINT8)sizeof (EFI_GUID); | |
} | |
// | |
// Set fields of the token for the request packet. | |
// | |
Xid = NET_RANDOM (NetRandomInitSeed ()); | |
Token.Packet->Dhcp4.Header.Xid = HTONL (Xid); | |
Token.Packet->Dhcp4.Header.Reserved = HTONS ((UINT16)((IsBCast) ? 0x8000 : 0x0)); | |
CopyMem (&Token.Packet->Dhcp4.Header.ClientAddr, &Private->StationIp, sizeof (EFI_IPv4_ADDRESS)); | |
Token.RemotePort = Sport; | |
if (IsBCast) { | |
SetMem (&Token.RemoteAddress, sizeof (EFI_IPv4_ADDRESS), 0xff); | |
} else { | |
CopyMem (&Token.RemoteAddress, DestIp, sizeof (EFI_IPv4_ADDRESS)); | |
} | |
CopyMem (&Token.GatewayAddress, &Private->GatewayIp, sizeof (EFI_IPv4_ADDRESS)); | |
if (!IsBCast) { | |
Token.ListenPointCount = 1; | |
Token.ListenPoints = &ListenPoint; | |
Token.ListenPoints[0].ListenPort = PXEBC_BS_DISCOVER_PORT; | |
CopyMem (&Token.ListenPoints[0].ListenAddress, &Private->StationIp, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&Token.ListenPoints[0].SubnetMask, &Private->SubnetMask, sizeof (EFI_IPv4_ADDRESS)); | |
} | |
// | |
// Send out the request packet to discover the bootfile. | |
// | |
for (TryIndex = 1; TryIndex <= PXEBC_BOOT_REQUEST_RETRIES; TryIndex++) { | |
Token.TimeoutValue = (UINT16)(PXEBC_BOOT_REQUEST_TIMEOUT * TryIndex); | |
Token.Packet->Dhcp4.Header.Seconds = (UINT16)(PXEBC_BOOT_REQUEST_TIMEOUT * (TryIndex - 1)); | |
Status = Dhcp4->TransmitReceive (Dhcp4, &Token); | |
if (Token.Status != EFI_TIMEOUT) { | |
break; | |
} | |
} | |
if (TryIndex > PXEBC_BOOT_REQUEST_RETRIES) { | |
// | |
// No server response our PXE request | |
// | |
Status = EFI_TIMEOUT; | |
} | |
if (!EFI_ERROR (Status)) { | |
RepIndex = 0; | |
SrvIndex = 0; | |
Response = Token.ResponseList; | |
// | |
// Find the right PXE Reply according to server address. | |
// | |
while (RepIndex < Token.ResponseCount) { | |
if (Response->Length > PXEBC_DHCP4_PACKET_MAX_SIZE) { | |
SrvIndex = 0; | |
RepIndex++; | |
Response = (EFI_DHCP4_PACKET *)((UINT8 *)Response + Response->Size); | |
continue; | |
} | |
while (SrvIndex < IpCount) { | |
if (SrvList[SrvIndex].AcceptAnyResponse) { | |
break; | |
} | |
if ((SrvList[SrvIndex].Type == Type) && | |
EFI_IP4_EQUAL (&Response->Dhcp4.Header.ServerAddr, &SrvList[SrvIndex].IpAddr)) | |
{ | |
break; | |
} | |
SrvIndex++; | |
} | |
if ((IpCount != SrvIndex) || (IpCount == 0)) { | |
break; | |
} | |
SrvIndex = 0; | |
RepIndex++; | |
Response = (EFI_DHCP4_PACKET *)((UINT8 *)Response + Response->Size); | |
} | |
if (RepIndex < Token.ResponseCount) { | |
// | |
// Cache the right PXE reply packet here, set valid flag later. | |
// Especially for PXE discover packet, store it into mode data here. | |
// | |
if (Private->IsDoDiscover) { | |
Status = PxeBcCacheDhcp4Packet (&Private->PxeReply.Dhcp4.Packet.Ack, Response); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
CopyMem (&Mode->PxeDiscover, &Token.Packet->Dhcp4, Token.Packet->Length); | |
} else { | |
Status = PxeBcCacheDhcp4Packet (&Private->ProxyOffer.Dhcp4.Packet.Offer, Response); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
} | |
} else { | |
// | |
// Not found the right PXE reply packet. | |
// | |
Status = EFI_NOT_FOUND; | |
} | |
} | |
ON_EXIT: | |
if (Token.ResponseList != NULL) { | |
FreePool (Token.ResponseList); | |
} | |
if (Token.Packet != NULL) { | |
FreePool (Token.Packet); | |
} | |
return Status; | |
} | |
/** | |
Switch the Ip4 policy to static. | |
@param[in] Private The pointer to PXEBC_PRIVATE_DATA. | |
@retval EFI_SUCCESS The policy is already configured to static. | |
@retval Others Other error as indicated.. | |
**/ | |
EFI_STATUS | |
PxeBcSetIp4Policy ( | |
IN PXEBC_PRIVATE_DATA *Private | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2; | |
EFI_IP4_CONFIG2_POLICY Policy; | |
UINTN DataSize; | |
Ip4Config2 = Private->Ip4Config2; | |
DataSize = sizeof (EFI_IP4_CONFIG2_POLICY); | |
Status = Ip4Config2->GetData ( | |
Ip4Config2, | |
Ip4Config2DataTypePolicy, | |
&DataSize, | |
&Policy | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (Policy != Ip4Config2PolicyStatic) { | |
Policy = Ip4Config2PolicyStatic; | |
Status = Ip4Config2->SetData ( | |
Ip4Config2, | |
Ip4Config2DataTypePolicy, | |
sizeof (EFI_IP4_CONFIG2_POLICY), | |
&Policy | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Start the D.O.R.A DHCPv4 process to acquire the IPv4 address and other PXE boot information. | |
@param[in] Private Pointer to PxeBc private data. | |
@param[in] Dhcp4 Pointer to the EFI_DHCP4_PROTOCOL | |
@retval EFI_SUCCESS The D.O.R.A process successfully finished. | |
@retval Others Failed to finish the D.O.R.A process. | |
**/ | |
EFI_STATUS | |
PxeBcDhcp4Dora ( | |
IN PXEBC_PRIVATE_DATA *Private, | |
IN EFI_DHCP4_PROTOCOL *Dhcp4 | |
) | |
{ | |
EFI_PXE_BASE_CODE_MODE *PxeMode; | |
EFI_DHCP4_CONFIG_DATA Config; | |
EFI_DHCP4_MODE_DATA Mode; | |
EFI_DHCP4_PACKET_OPTION *OptList[PXEBC_DHCP4_OPTION_MAX_NUM]; | |
UINT8 Buffer[PXEBC_DHCP4_OPTION_MAX_SIZE]; | |
UINT32 OptCount; | |
EFI_STATUS Status; | |
ASSERT (Dhcp4 != NULL); | |
Status = EFI_SUCCESS; | |
PxeMode = Private->PxeBc.Mode; | |
// | |
// Build option list for the request packet. | |
// | |
OptCount = PxeBcBuildDhcp4Options (Private, OptList, Buffer, FALSE); | |
ASSERT (OptCount > 0); | |
ZeroMem (&Mode, sizeof (EFI_DHCP4_MODE_DATA)); | |
ZeroMem (&Config, sizeof (EFI_DHCP4_CONFIG_DATA)); | |
Config.OptionCount = OptCount; | |
Config.OptionList = OptList; | |
Config.Dhcp4Callback = PxeBcDhcp4CallBack; | |
Config.CallbackContext = Private; | |
Config.DiscoverTryCount = PXEBC_DHCP_RETRIES; | |
Config.DiscoverTimeout = mPxeDhcpTimeout; | |
// | |
// Configure the DHCPv4 instance for PXE boot. | |
// | |
Status = Dhcp4->Configure (Dhcp4, &Config); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
// | |
// Initialize the record fields for DHCPv4 offer in private data. | |
// | |
Private->IsProxyRecved = FALSE; | |
Private->OfferNum = 0; | |
ZeroMem (Private->OfferCount, sizeof (Private->OfferCount)); | |
ZeroMem (Private->OfferIndex, sizeof (Private->OfferIndex)); | |
Status = Dhcp4->Start (Dhcp4, NULL); | |
if (EFI_ERROR (Status)) { | |
if (Status == EFI_ICMP_ERROR) { | |
PxeMode->IcmpErrorReceived = TRUE; | |
} | |
if ((Status == EFI_TIMEOUT) && (Private->OfferNum > 0)) { | |
Status = EFI_NO_RESPONSE; | |
} | |
goto ON_EXIT; | |
} | |
// | |
// Get the acquired IPv4 address and store them. | |
// | |
Status = Dhcp4->GetModeData (Dhcp4, &Mode); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
ASSERT (Mode.State == Dhcp4Bound); | |
CopyMem (&Private->StationIp, &Mode.ClientAddress, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&Private->SubnetMask, &Mode.SubnetMask, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&Private->GatewayIp, &Mode.RouterAddress, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&PxeMode->StationIp, &Private->StationIp, sizeof (EFI_IPv4_ADDRESS)); | |
CopyMem (&PxeMode->SubnetMask, &Private->SubnetMask, sizeof (EFI_IPv4_ADDRESS)); | |
Status = PxeBcFlushStationIp (Private, &Private->StationIp, &Private->SubnetMask); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
// | |
// Check the selected offer whether BINL retry is needed. | |
// | |
Status = PxeBcHandleDhcp4Offer (Private); | |
AsciiPrint ("\n Station IP address is "); | |
PxeBcShowIp4Addr (&Private->StationIp.v4); | |
AsciiPrint ("\n"); | |
ON_EXIT: | |
if (EFI_ERROR (Status)) { | |
Dhcp4->Stop (Dhcp4); | |
Dhcp4->Configure (Dhcp4, NULL); | |
} else { | |
ZeroMem (&Config, sizeof (EFI_DHCP4_CONFIG_DATA)); | |
Dhcp4->Configure (Dhcp4, &Config); | |
Private->IsAddressOk = TRUE; | |
} | |
return Status; | |
} |