diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcDhcp4.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcDhcp4.c | 1756 |
1 files changed, 1756 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcDhcp4.c b/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcDhcp4.c new file mode 100644 index 00000000..f3028586 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcDhcp4.c @@ -0,0 +1,1756 @@ +/** @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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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; +} |