/** @file Functions implementation related with DHCPv6 for UefiPxeBc Driver. (C) Copyright 2014 Hewlett-Packard Development Company, L.P.
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PxeBcImpl.h" // // Well-known multi-cast address defined in section-24.1 of rfc-3315 // // ALL_DHCP_Relay_Agents_and_Servers address: FF02::1:2 // EFI_IPv6_ADDRESS mAllDhcpRelayAndServersAddress = {{0xFF, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2}}; /** Parse out a DHCPv6 option by OptTag, and find the position in buffer. @param[in] Buffer The pointer to the option buffer. @param[in] Length Length of the option buffer. @param[in] OptTag The required option tag. @retval NULL Failed to parse the required option. @retval Others The position of the required option in buffer. **/ EFI_DHCP6_PACKET_OPTION * PxeBcParseDhcp6Options ( IN UINT8 *Buffer, IN UINT32 Length, IN UINT16 OptTag ) { EFI_DHCP6_PACKET_OPTION *Option; UINT32 Offset; Option = (EFI_DHCP6_PACKET_OPTION *) Buffer; Offset = 0; // // OpLen and OpCode here are both stored in network order. // while (Offset < Length) { if (NTOHS (Option->OpCode) == OptTag) { return Option; } Offset += (NTOHS(Option->OpLen) + 4); Option = (EFI_DHCP6_PACKET_OPTION *) (Buffer + Offset); } return NULL; } /** Build the options buffer for the DHCPv6 request packet. @param[in] Private The pointer to PxeBc private data. @param[out] OptList The pointer to the option pointer array. @param[in] Buffer The pointer to the buffer to contain the option list. @return Index The count of the built-in options. **/ UINT32 PxeBcBuildDhcp6Options ( IN PXEBC_PRIVATE_DATA *Private, OUT EFI_DHCP6_PACKET_OPTION **OptList, IN UINT8 *Buffer ) { PXEBC_DHCP6_OPTION_ENTRY OptEnt; UINT32 Index; UINT16 Value; Index = 0; OptList[0] = (EFI_DHCP6_PACKET_OPTION *) Buffer; // // Append client option request option // OptList[Index]->OpCode = HTONS (DHCP6_OPT_ORO); OptList[Index]->OpLen = HTONS (8); OptEnt.Oro = (PXEBC_DHCP6_OPTION_ORO *) OptList[Index]->Data; OptEnt.Oro->OpCode[0] = HTONS(DHCP6_OPT_BOOT_FILE_URL); OptEnt.Oro->OpCode[1] = HTONS(DHCP6_OPT_BOOT_FILE_PARAM); OptEnt.Oro->OpCode[2] = HTONS(DHCP6_OPT_DNS_SERVERS); OptEnt.Oro->OpCode[3] = HTONS(DHCP6_OPT_VENDOR_CLASS); Index++; OptList[Index] = GET_NEXT_DHCP6_OPTION (OptList[Index - 1]); // // Append client network device interface option // OptList[Index]->OpCode = HTONS (DHCP6_OPT_UNDI); OptList[Index]->OpLen = HTONS ((UINT16)3); OptEnt.Undi = (PXEBC_DHCP6_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_DHCP6_OPTION (OptList[Index - 1]); // // Append client system architecture option // OptList[Index]->OpCode = HTONS (DHCP6_OPT_ARCH); OptList[Index]->OpLen = HTONS ((UINT16) sizeof (PXEBC_DHCP6_OPTION_ARCH)); OptEnt.Arch = (PXEBC_DHCP6_OPTION_ARCH *) OptList[Index]->Data; Value = HTONS (EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE); CopyMem (&OptEnt.Arch->Type, &Value, sizeof (UINT16)); Index++; OptList[Index] = GET_NEXT_DHCP6_OPTION (OptList[Index - 1]); // // Append vendor class option to store the PXE class identifier. // OptList[Index]->OpCode = HTONS (DHCP6_OPT_VENDOR_CLASS); OptList[Index]->OpLen = HTONS ((UINT16) sizeof (PXEBC_DHCP6_OPTION_VENDOR_CLASS)); OptEnt.VendorClass = (PXEBC_DHCP6_OPTION_VENDOR_CLASS *) OptList[Index]->Data; OptEnt.VendorClass->Vendor = HTONL (PXEBC_DHCP6_ENTERPRISE_NUM); OptEnt.VendorClass->ClassLen = HTONS ((UINT16) sizeof (PXEBC_CLASS_ID)); CopyMem ( &OptEnt.VendorClass->ClassId, DEFAULT_CLASS_ID_DATA, sizeof (PXEBC_CLASS_ID) ); PxeBcUintnToAscDecWithFormat ( EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE, OptEnt.VendorClass->ClassId.ArchitectureType, sizeof (OptEnt.VendorClass->ClassId.ArchitectureType) ); if (Private->Nii != NULL) { CopyMem ( OptEnt.VendorClass->ClassId.InterfaceName, Private->Nii->StringId, sizeof (OptEnt.VendorClass->ClassId.InterfaceName) ); PxeBcUintnToAscDecWithFormat ( Private->Nii->MajorVer, OptEnt.VendorClass->ClassId.UndiMajor, sizeof (OptEnt.VendorClass->ClassId.UndiMajor) ); PxeBcUintnToAscDecWithFormat ( Private->Nii->MinorVer, OptEnt.VendorClass->ClassId.UndiMinor, sizeof (OptEnt.VendorClass->ClassId.UndiMinor) ); } Index++; return Index; } /** Cache the DHCPv6 packet. @param[in] Dst The pointer to the cache buffer for DHCPv6 packet. @param[in] Src The pointer to the DHCPv6 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 PxeBcCacheDhcp6Packet ( IN EFI_DHCP6_PACKET *Dst, IN EFI_DHCP6_PACKET *Src ) { if (Dst->Size < Src->Length) { return EFI_BUFFER_TOO_SMALL; } CopyMem (&Dst->Dhcp6, &Src->Dhcp6, Src->Length); Dst->Length = Src->Length; return EFI_SUCCESS; } /** Retrieve the boot server address using the EFI_DNS6_PROTOCOL. @param[in] Private Pointer to PxeBc private data. @param[in] HostName Pointer to buffer containing hostname. @param[out] IpAddress On output, pointer to buffer containing IPv6 address. @retval EFI_SUCCESS Operation succeeded. @retval EFI_OUT_OF_RESOURCES Failed to allocate needed resources. @retval EFI_DEVICE_ERROR An unexpected network error occurred. @retval Others Other errors as indicated. **/ EFI_STATUS PxeBcDns6 ( IN PXEBC_PRIVATE_DATA *Private, IN CHAR16 *HostName, OUT EFI_IPv6_ADDRESS *IpAddress ) { EFI_STATUS Status; EFI_DNS6_PROTOCOL *Dns6; EFI_DNS6_CONFIG_DATA Dns6ConfigData; EFI_DNS6_COMPLETION_TOKEN Token; EFI_HANDLE Dns6Handle; EFI_IPv6_ADDRESS *DnsServerList; BOOLEAN IsDone; Dns6 = NULL; Dns6Handle = NULL; DnsServerList = Private->DnsServer; ZeroMem (&Token, sizeof (EFI_DNS6_COMPLETION_TOKEN)); // // Create a DNSv6 child instance and get the protocol. // Status = NetLibCreateServiceChild ( Private->Controller, Private->Image, &gEfiDns6ServiceBindingProtocolGuid, &Dns6Handle ); if (EFI_ERROR (Status)) { goto Exit; } Status = gBS->OpenProtocol ( Dns6Handle, &gEfiDns6ProtocolGuid, (VOID **) &Dns6, Private->Image, Private->Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (EFI_ERROR (Status)) { goto Exit; } // // Configure DNS6 instance for the DNS server address and protocol. // ZeroMem (&Dns6ConfigData, sizeof (EFI_DNS6_CONFIG_DATA)); Dns6ConfigData.DnsServerCount = 1; Dns6ConfigData.DnsServerList = DnsServerList; Dns6ConfigData.EnableDnsCache = TRUE; Dns6ConfigData.Protocol = EFI_IP_PROTO_UDP; IP6_COPY_ADDRESS (&Dns6ConfigData.StationIp, &Private->TmpStationIp.v6); Status = Dns6->Configure ( Dns6, &Dns6ConfigData ); if (EFI_ERROR (Status)) { goto Exit; } Token.Status = EFI_NOT_READY; IsDone = FALSE; // // Create event to set the IsDone flag when name resolution is finished. // Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, PxeBcCommonNotify, &IsDone, &Token.Event ); if (EFI_ERROR (Status)) { goto Exit; } // // Start asynchronous name resolution. // Status = Dns6->HostNameToIp (Dns6, HostName, &Token); if (EFI_ERROR (Status)) { goto Exit; } while (!IsDone) { Dns6->Poll (Dns6); } // // Name resolution is done, check result. // Status = Token.Status; if (!EFI_ERROR (Status)) { if (Token.RspData.H2AData == NULL) { Status = EFI_DEVICE_ERROR; goto Exit; } if (Token.RspData.H2AData->IpCount == 0 || Token.RspData.H2AData->IpList == NULL) { Status = EFI_DEVICE_ERROR; goto Exit; } // // We just return the first IPv6 address from DNS protocol. // IP6_COPY_ADDRESS (IpAddress, Token.RspData.H2AData->IpList); Status = EFI_SUCCESS; } Exit: FreePool (HostName); if (Token.Event != NULL) { gBS->CloseEvent (Token.Event); } if (Token.RspData.H2AData != NULL) { if (Token.RspData.H2AData->IpList != NULL) { FreePool (Token.RspData.H2AData->IpList); } FreePool (Token.RspData.H2AData); } if (Dns6 != NULL) { Dns6->Configure (Dns6, NULL); gBS->CloseProtocol ( Dns6Handle, &gEfiDns6ProtocolGuid, Private->Image, Private->Controller ); } if (Dns6Handle != NULL) { NetLibDestroyServiceChild ( Private->Controller, Private->Image, &gEfiDns6ServiceBindingProtocolGuid, Dns6Handle ); } if (DnsServerList != NULL) { FreePool (DnsServerList); } return Status; } /** Parse the Boot File URL option. @param[in] Private Pointer to PxeBc private data. @param[out] FileName The pointer to the boot file name. @param[in, out] SrvAddr The pointer to the boot server address. @param[in] BootFile The pointer to the boot file URL option data. @param[in] Length The length of the boot file URL option data. @retval EFI_ABORTED User cancel operation. @retval EFI_SUCCESS Selected the boot menu successfully. @retval EFI_NOT_READY Read the input key from the keyboard has not finish. **/ EFI_STATUS PxeBcExtractBootFileUrl ( IN PXEBC_PRIVATE_DATA *Private, OUT UINT8 **FileName, IN OUT EFI_IPv6_ADDRESS *SrvAddr, IN CHAR8 *BootFile, IN UINT16 Length ) { UINT16 PrefixLen; CHAR8 *BootFileNamePtr; CHAR8 *BootFileName; UINT16 BootFileNameLen; CHAR8 *TmpStr; CHAR8 TmpChar; CHAR8 *ServerAddressOption; CHAR8 *ServerAddress; CHAR8 *ModeStr; CHAR16 *HostName; BOOLEAN IpExpressedUrl; UINTN Len; EFI_STATUS Status; IpExpressedUrl = TRUE; // // The format of the Boot File URL option is: // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | OPT_BOOTFILE_URL | option-len | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | | // . bootfile-url (variable length) . // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // // Based upon RFC 5970 and UEFI 2.6, bootfile-url format can be // tftp://[SERVER_ADDRESS]/BOOTFILE_NAME or tftp://domain_name/BOOTFILE_NAME // As an example where the BOOTFILE_NAME is the EFI loader and // SERVER_ADDRESS is the ASCII encoding of an IPV6 address. // PrefixLen = (UINT16) AsciiStrLen (PXEBC_DHCP6_BOOT_FILE_URL_PREFIX); if (Length <= PrefixLen || CompareMem (BootFile, PXEBC_DHCP6_BOOT_FILE_URL_PREFIX, PrefixLen) != 0) { return EFI_NOT_FOUND; } BootFile = BootFile + PrefixLen; Length = (UINT16) (Length - PrefixLen); TmpStr = (CHAR8 *) AllocateZeroPool (Length + 1); if (TmpStr == NULL) { return EFI_OUT_OF_RESOURCES; } CopyMem (TmpStr, BootFile, Length); TmpStr[Length] = '\0'; // // Get the part of SERVER_ADDRESS string. // ServerAddressOption = TmpStr; if (*ServerAddressOption == PXEBC_ADDR_START_DELIMITER) { ServerAddressOption ++; ServerAddress = ServerAddressOption; while (*ServerAddress != '\0' && *ServerAddress != PXEBC_ADDR_END_DELIMITER) { ServerAddress++; } if (*ServerAddress != PXEBC_ADDR_END_DELIMITER) { FreePool (TmpStr); return EFI_INVALID_PARAMETER; } *ServerAddress = '\0'; // // Convert the string of server address to Ipv6 address format and store it. // Status = NetLibAsciiStrToIp6 (ServerAddressOption, SrvAddr); if (EFI_ERROR (Status)) { FreePool (TmpStr); return Status; } } else { IpExpressedUrl = FALSE; ServerAddress = ServerAddressOption; while (*ServerAddress != '\0' && *ServerAddress != PXEBC_TFTP_URL_SEPARATOR) { ServerAddress++; } if (*ServerAddress != PXEBC_TFTP_URL_SEPARATOR) { FreePool (TmpStr); return EFI_INVALID_PARAMETER; } *ServerAddress = '\0'; Len = AsciiStrSize (ServerAddressOption); HostName = AllocateZeroPool (Len * sizeof (CHAR16)); if (HostName == NULL) { FreePool (TmpStr); return EFI_OUT_OF_RESOURCES; } AsciiStrToUnicodeStrS ( ServerAddressOption, HostName, Len ); // // Perform DNS resolution. // Status = PxeBcDns6 (Private,HostName, SrvAddr); if (EFI_ERROR (Status)) { FreePool (TmpStr); return Status; } } // // Get the part of BOOTFILE_NAME string. // BootFileNamePtr = (CHAR8*)((UINTN)ServerAddress + 1); if (IpExpressedUrl) { if (*BootFileNamePtr != PXEBC_TFTP_URL_SEPARATOR) { FreePool (TmpStr); return EFI_INVALID_PARAMETER; } ++BootFileNamePtr; } BootFileNameLen = (UINT16)(Length - (UINT16) ((UINTN)BootFileNamePtr - (UINTN)TmpStr) + 1); if (BootFileNameLen != 0 || FileName != NULL) { // // Remove trailing mode=octet if present and ignore. All other modes are // invalid for netboot6, so reject them. // ModeStr = AsciiStrStr (BootFileNamePtr, ";mode=octet"); if (ModeStr != NULL && *(ModeStr + AsciiStrLen (";mode=octet")) == '\0') { *ModeStr = '\0'; } else if (AsciiStrStr (BootFileNamePtr, ";mode=") != NULL) { FreePool (TmpStr); return EFI_INVALID_PARAMETER; } // // Extract boot file name from URL. // BootFileName = (CHAR8 *) AllocateZeroPool (BootFileNameLen); if (BootFileName == NULL) { FreePool (TmpStr); return EFI_OUT_OF_RESOURCES; } *FileName = (UINT8*) BootFileName; // // Decode percent-encoding in boot file name. // while (*BootFileNamePtr != '\0') { if (*BootFileNamePtr == '%') { TmpChar = *(BootFileNamePtr+ 3); *(BootFileNamePtr+ 3) = '\0'; *BootFileName = (UINT8) AsciiStrHexToUintn ((CHAR8*)(BootFileNamePtr + 1)); BootFileName++; *(BootFileNamePtr+ 3) = TmpChar; BootFileNamePtr += 3; } else { *BootFileName = *BootFileNamePtr; BootFileName++; BootFileNamePtr++; } } *BootFileName = '\0'; } FreePool (TmpStr); return EFI_SUCCESS; } /** Parse the Boot File Parameter option. @param[in] BootFilePara The pointer to boot file parameter option data. @param[out] BootFileSize The pointer to the parsed boot file size. @retval EFI_SUCCESS Successfully obtained the boot file size from parameter option. @retval EFI_NOT_FOUND Failed to extract the boot file size from parameter option. **/ EFI_STATUS PxeBcExtractBootFileParam ( IN CHAR8 *BootFilePara, OUT UINT16 *BootFileSize ) { UINT16 Length; UINT8 Index; UINT8 Digit; UINT32 Size; CopyMem (&Length, BootFilePara, sizeof (UINT16)); Length = NTOHS (Length); // // The BootFile Size should be 1~5 byte ASCII strings // if (Length < 1 || Length > 5) { return EFI_NOT_FOUND; } // // Extract the value of BootFile Size. // BootFilePara = BootFilePara + sizeof (UINT16); Size = 0; for (Index = 0; Index < Length; Index++) { if (EFI_ERROR (PxeBcUniHexToUint8 (&Digit, *(BootFilePara + Index)))) { return EFI_NOT_FOUND; } Size = (Size + Digit) * 10; } Size = Size / 10; if (Size > PXEBC_DHCP6_MAX_BOOT_FILE_SIZE) { return EFI_NOT_FOUND; } *BootFileSize = (UINT16) Size; return EFI_SUCCESS; } /** Parse the cached DHCPv6 packet, including all the options. @param[in] Cache6 The pointer to a cached DHCPv6 packet. @retval EFI_SUCCESS Parsed the DHCPv6 packet successfully. @retval EFI_DEVICE_ERROR Failed to parse and invalid the packet. **/ EFI_STATUS PxeBcParseDhcp6Packet ( IN PXEBC_DHCP6_PACKET_CACHE *Cache6 ) { EFI_DHCP6_PACKET *Offer; EFI_DHCP6_PACKET_OPTION **Options; EFI_DHCP6_PACKET_OPTION *Option; PXEBC_OFFER_TYPE OfferType; BOOLEAN IsProxyOffer; BOOLEAN IsPxeOffer; UINT32 Offset; UINT32 Length; UINT32 EnterpriseNum; IsProxyOffer = TRUE; IsPxeOffer = FALSE; Offer = &Cache6->Packet.Offer; Options = Cache6->OptList; ZeroMem (Cache6->OptList, sizeof (Cache6->OptList)); Option = (EFI_DHCP6_PACKET_OPTION *) (Offer->Dhcp6.Option); Offset = 0; Length = GET_DHCP6_OPTION_SIZE (Offer); // // OpLen and OpCode here are both stored in network order, since they are from original packet. // while (Offset < Length) { if (NTOHS (Option->OpCode) == DHCP6_OPT_IA_NA) { Options[PXEBC_DHCP6_IDX_IA_NA] = Option; } else if (NTOHS (Option->OpCode) == DHCP6_OPT_BOOT_FILE_URL) { // // The server sends this option to inform the client about an URL to a boot file. // Options[PXEBC_DHCP6_IDX_BOOT_FILE_URL] = Option; } else if (NTOHS (Option->OpCode) == DHCP6_OPT_BOOT_FILE_PARAM) { Options[PXEBC_DHCP6_IDX_BOOT_FILE_PARAM] = Option; } else if (NTOHS (Option->OpCode) == DHCP6_OPT_VENDOR_CLASS) { Options[PXEBC_DHCP6_IDX_VENDOR_CLASS] = Option; } else if (NTOHS (Option->OpCode) == DHCP6_OPT_DNS_SERVERS) { Options[PXEBC_DHCP6_IDX_DNS_SERVER] = Option; } Offset += (NTOHS (Option->OpLen) + 4); Option = (EFI_DHCP6_PACKET_OPTION *) (Offer->Dhcp6.Option + Offset); } // // The offer with assigned client address is NOT a proxy offer. // An ia_na option, embedded with valid ia_addr option and a status_code of success. // Option = Options[PXEBC_DHCP6_IDX_IA_NA]; if (Option != NULL) { Option = PxeBcParseDhcp6Options ( Option->Data + 12, NTOHS (Option->OpLen), DHCP6_OPT_STATUS_CODE ); if ((Option != NULL && Option->Data[0] == 0) || (Option == NULL)) { IsProxyOffer = FALSE; } } // // The offer with "PXEClient" is a pxe offer. // Option = Options[PXEBC_DHCP6_IDX_VENDOR_CLASS]; EnterpriseNum = HTONL(PXEBC_DHCP6_ENTERPRISE_NUM); if (Option != NULL && NTOHS(Option->OpLen) >= 13 && CompareMem (Option->Data, &EnterpriseNum, sizeof (UINT32)) == 0 && CompareMem (&Option->Data[6], DEFAULT_CLASS_ID_DATA, 9) == 0) { IsPxeOffer = TRUE; } // // Determine offer type of the dhcp6 packet. // if (IsPxeOffer) { // // It's a binl offer only with PXEClient. // OfferType = IsProxyOffer ? PxeOfferTypeProxyBinl : PxeOfferTypeDhcpBinl; } else { // // It's a dhcp only offer, which is a pure dhcp6 offer packet. // OfferType = PxeOfferTypeDhcpOnly; } Cache6->OfferType = OfferType; return EFI_SUCCESS; } /** Cache the DHCPv6 ack packet, and parse it on demand. @param[in] Private The pointer to PxeBc private data. @param[in] Ack The pointer to the DHCPv6 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 PxeBcCopyDhcp6Ack ( IN PXEBC_PRIVATE_DATA *Private, IN EFI_DHCP6_PACKET *Ack, IN BOOLEAN Verified ) { EFI_PXE_BASE_CODE_MODE *Mode; EFI_STATUS Status; Mode = Private->PxeBc.Mode; Status = PxeBcCacheDhcp6Packet (&Private->DhcpAck.Dhcp6.Packet.Ack, Ack); if (EFI_ERROR (Status)) { return Status; } if (Verified) { // // Parse the ack packet and store it into mode data if needed. // PxeBcParseDhcp6Packet (&Private->DhcpAck.Dhcp6); CopyMem (&Mode->DhcpAck.Dhcpv6, &Ack->Dhcp6, Ack->Length); Mode->DhcpAckReceived = TRUE; } return EFI_SUCCESS; } /** Cache the DHCPv6 proxy offer packet according to the received order. @param[in] Private The 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 PxeBcCopyDhcp6Proxy ( IN PXEBC_PRIVATE_DATA *Private, IN UINT32 OfferIndex ) { EFI_PXE_BASE_CODE_MODE *Mode; EFI_DHCP6_PACKET *Offer; EFI_STATUS Status; ASSERT (OfferIndex < Private->OfferNum); ASSERT (OfferIndex < PXEBC_OFFER_MAX_NUM); Mode = Private->PxeBc.Mode; Offer = &Private->OfferBuffer[OfferIndex].Dhcp6.Packet.Offer; // // Cache the proxy offer packet and parse it. // Status = PxeBcCacheDhcp6Packet (&Private->ProxyOffer.Dhcp6.Packet.Offer, Offer); if (EFI_ERROR(Status)) { return Status; } PxeBcParseDhcp6Packet (&Private->ProxyOffer.Dhcp6); // // Store this packet into mode data. // CopyMem (&Mode->ProxyOffer.Dhcpv6, &Offer->Dhcp6, Offer->Length); Mode->ProxyOfferReceived = TRUE; return EFI_SUCCESS; } /** Seek the address of the first byte of the option header. @param[in] Buf The pointer to the buffer. @param[in] SeekLen The length to seek. @param[in] OptType The option type. @retval NULL If it failed to seek the option. @retval others The position to the option. **/ UINT8 * PxeBcDhcp6SeekOption ( IN UINT8 *Buf, IN UINT32 SeekLen, IN UINT16 OptType ) { UINT8 *Cursor; UINT8 *Option; UINT16 DataLen; UINT16 OpCode; Option = NULL; Cursor = Buf; while (Cursor < Buf + SeekLen) { OpCode = ReadUnaligned16 ((UINT16 *) Cursor); if (OpCode == HTONS (OptType)) { Option = Cursor; break; } DataLen = NTOHS (ReadUnaligned16 ((UINT16 *) (Cursor + 2))); Cursor += (DataLen + 4); } return Option; } /** Build and send out the request packet for the bootfile, and parse the reply. @param[in] Private The pointer to PxeBc private data. @param[in] Index PxeBc option boot item type. @retval EFI_SUCCESS Successfully discovered the boot file. @retval EFI_OUT_OF_RESOURCES Failed to allocate resources. @retval EFI_NOT_FOUND Can't get the PXE reply packet. @retval Others Failed to discover the boot file. **/ EFI_STATUS PxeBcRequestBootService ( IN PXEBC_PRIVATE_DATA *Private, IN UINT32 Index ) { EFI_PXE_BASE_CODE_UDP_PORT SrcPort; EFI_PXE_BASE_CODE_UDP_PORT DestPort; EFI_PXE_BASE_CODE_PROTOCOL *PxeBc; EFI_PXE_BASE_CODE_DHCPV6_PACKET *Discover; UINTN DiscoverLen; EFI_DHCP6_PACKET *Request; UINTN RequestLen; EFI_DHCP6_PACKET *Reply; UINT8 *RequestOpt; UINT8 *DiscoverOpt; UINTN ReadSize; UINT16 OpFlags; UINT16 OpCode; UINT16 OpLen; EFI_STATUS Status; EFI_DHCP6_PACKET *IndexOffer; UINT8 *Option; PxeBc = &Private->PxeBc; Request = Private->Dhcp6Request; IndexOffer = &Private->OfferBuffer[Index].Dhcp6.Packet.Offer; SrcPort = PXEBC_BS_DISCOVER_PORT; DestPort = PXEBC_BS_DISCOVER_PORT; OpFlags = 0; if (Request == NULL) { return EFI_DEVICE_ERROR; } Discover = AllocateZeroPool (sizeof (EFI_PXE_BASE_CODE_DHCPV6_PACKET)); if (Discover == NULL) { return EFI_OUT_OF_RESOURCES; } // // Build the request packet by the cached request packet before. // Discover->TransactionId = IndexOffer->Dhcp6.Header.TransactionId; Discover->MessageType = Request->Dhcp6.Header.MessageType; RequestOpt = Request->Dhcp6.Option; DiscoverOpt = Discover->DhcpOptions; DiscoverLen = sizeof (EFI_DHCP6_HEADER); RequestLen = DiscoverLen; // // Find Server ID Option from ProxyOffer. // if (Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeProxyBinl) { Option = PxeBcDhcp6SeekOption ( IndexOffer->Dhcp6.Option, IndexOffer->Length - 4, DHCP6_OPT_SERVER_ID ); if (Option == NULL) { return EFI_NOT_FOUND; } // // Add Server ID Option. // OpLen = NTOHS (((EFI_DHCP6_PACKET_OPTION *) Option)->OpLen); CopyMem (DiscoverOpt, Option, OpLen + 4); DiscoverOpt += (OpLen + 4); DiscoverLen += (OpLen + 4); } while (RequestLen < Request->Length) { OpCode = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpCode); OpLen = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpLen); if (OpCode != EFI_DHCP6_IA_TYPE_NA && OpCode != EFI_DHCP6_IA_TYPE_TA && OpCode != DHCP6_OPT_SERVER_ID ) { // // Copy all the options except IA option and Server ID // CopyMem (DiscoverOpt, RequestOpt, OpLen + 4); DiscoverOpt += (OpLen + 4); DiscoverLen += (OpLen + 4); } RequestOpt += (OpLen + 4); RequestLen += (OpLen + 4); } // // Update Elapsed option in the package // Option = PxeBcDhcp6SeekOption ( Discover->DhcpOptions, (UINT32)(RequestLen - 4), DHCP6_OPT_ELAPSED_TIME ); if (Option != NULL) { CalcElapsedTime (Private); WriteUnaligned16 ((UINT16*)(Option + 4), HTONS((UINT16) Private->ElapsedTime)); } Status = PxeBc->UdpWrite ( PxeBc, OpFlags, &Private->ServerIp, &DestPort, NULL, &Private->StationIp, &SrcPort, NULL, NULL, &DiscoverLen, (VOID *) Discover ); if (EFI_ERROR (Status)) { goto ON_ERROR; } // // Cache the right PXE reply packet here, set valid flag later. // Especially for PXE discover packet, store it into mode data here. // Reply = &Private->ProxyOffer.Dhcp6.Packet.Offer; ReadSize = (UINTN) Reply->Size; // // Start Udp6Read instance // Status = Private->Udp6Read->Configure (Private->Udp6Read, &Private->Udp6CfgData); if (EFI_ERROR (Status)) { goto ON_ERROR; } Status = PxeBc->UdpRead ( PxeBc, EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP | EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP, NULL, &SrcPort, &Private->ServerIp, &DestPort, NULL, NULL, &ReadSize, (VOID *) &Reply->Dhcp6 ); // // Stop Udp6Read instance // Private->Udp6Read->Configure (Private->Udp6Read, NULL); if (EFI_ERROR (Status)) { goto ON_ERROR; } // // Update length // Reply->Length = (UINT32) ReadSize; return EFI_SUCCESS; ON_ERROR: if (Discover != NULL) { FreePool (Discover); } return Status; } /** Retry to request bootfile name by the BINL offer. @param[in] Private The pointer to PxeBc private data. @param[in] Index The received order of offer packets. @retval EFI_SUCCESS Successfully retried a request for the bootfile name. @retval EFI_DEVICE_ERROR Failed to retry the bootfile name. **/ EFI_STATUS PxeBcRetryDhcp6Binl ( IN PXEBC_PRIVATE_DATA *Private, IN UINT32 Index ) { EFI_PXE_BASE_CODE_MODE *Mode; PXEBC_DHCP6_PACKET_CACHE *Offer; PXEBC_DHCP6_PACKET_CACHE *Cache6; EFI_STATUS Status; ASSERT (Index < PXEBC_OFFER_MAX_NUM); ASSERT (Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeDhcpBinl || Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeProxyBinl); Mode = Private->PxeBc.Mode; Private->IsDoDiscover = FALSE; Offer = &Private->OfferBuffer[Index].Dhcp6; if (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) { // // There is no BootFileUrl option in dhcp6 offer, so use servers multi-cast address instead. // CopyMem ( &Private->ServerIp.v6, &mAllDhcpRelayAndServersAddress, sizeof (EFI_IPv6_ADDRESS) ); } else { ASSERT (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL); // // Parse out the next server address from the last offer, and store it // Status = PxeBcExtractBootFileUrl ( Private, &Private->BootFileName, &Private->ServerIp.v6, (CHAR8 *) (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->Data), NTOHS (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->OpLen) ); if (EFI_ERROR (Status)) { return Status; } } // // Retry Dhcp6Binl again for the bootfile, and the reply cached into Private->ProxyOffer. // Status = PxeBcRequestBootService (Private, Index); if (EFI_ERROR (Status)) { return Status; } Cache6 = &Private->ProxyOffer.Dhcp6; Status = PxeBcParseDhcp6Packet (Cache6); if (EFI_ERROR (Status)) { return Status; } if (Cache6->OfferType != PxeOfferTypeProxyPxe10 && Cache6->OfferType != PxeOfferTypeProxyWfm11a && Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) { // // This BINL ack doesn't have discovery option set or multicast option set // or bootfile name specified. // return EFI_DEVICE_ERROR; } Mode->ProxyOfferReceived = TRUE; CopyMem ( &Mode->ProxyOffer.Dhcpv6, &Cache6->Packet.Offer.Dhcp6, Cache6->Packet.Offer.Length ); return EFI_SUCCESS; } /** Cache all the received DHCPv6 offers, and set OfferIndex and OfferCount. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @param[in] RcvdOffer The pointer to the received offer packet. @retval EFI_SUCCESS Cache and parse the packet successfully. @retval Others Operation failed. **/ EFI_STATUS PxeBcCacheDhcp6Offer ( IN PXEBC_PRIVATE_DATA *Private, IN EFI_DHCP6_PACKET *RcvdOffer ) { PXEBC_DHCP6_PACKET_CACHE *Cache6; EFI_DHCP6_PACKET *Offer; PXEBC_OFFER_TYPE OfferType; EFI_STATUS Status; Cache6 = &Private->OfferBuffer[Private->OfferNum].Dhcp6; Offer = &Cache6->Packet.Offer; // // Cache the content of DHCPv6 packet firstly. // Status = PxeBcCacheDhcp6Packet (Offer, RcvdOffer); if (EFI_ERROR (Status)) { return Status; } // // Validate the DHCPv6 packet, and parse the options and offer type. // if (EFI_ERROR (PxeBcParseDhcp6Packet (Cache6))) { return EFI_ABORTED; } // // Determine whether cache the current offer by type, and record OfferIndex and OfferCount. // OfferType = Cache6->OfferType; ASSERT (OfferType < PxeOfferTypeMax); ASSERT (Private->OfferCount[OfferType] < PXEBC_OFFER_MAX_NUM); if (IS_PROXY_OFFER (OfferType)) { // // 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 DHCPv6 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 DHCPv6 offer, and record SelectIndex and SelectProxyType. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. **/ VOID PxeBcSelectDhcp6Offer ( IN PXEBC_PRIVATE_DATA *Private ) { UINT32 Index; UINT32 OfferIndex; PXEBC_OFFER_TYPE OfferType; 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].Dhcp6.OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL) { Private->SelectIndex = OfferIndex + 1; break; } } } } else { // // Select offer by received order. // for (Index = 0; Index < Private->OfferNum; Index++) { OfferType = Private->OfferBuffer[Index].Dhcp6.OfferType; if (IS_PROXY_OFFER (OfferType)) { // // Skip proxy offers // continue; } if (!Private->IsProxyRecved && OfferType == PxeOfferTypeDhcpOnly && Private->OfferBuffer[Index].Dhcp6.OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) { // // Skip if DhcpOnly offer without any other proxy offers or bootfilename. // continue; } Private->SelectIndex = Index + 1; break; } } } /** Handle the DHCPv6 offer packet. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @retval EFI_SUCCESS Handled the DHCPv6 offer packet successfully. @retval EFI_NO_RESPONSE No response to the following request packet. @retval EFI_OUT_OF_RESOURCES Failed to allocate resources. @retval EFI_BUFFER_TOO_SMALL Can't cache the offer pacet. **/ EFI_STATUS PxeBcHandleDhcp6Offer ( IN PXEBC_PRIVATE_DATA *Private ) { PXEBC_DHCP6_PACKET_CACHE *Cache6; EFI_STATUS Status; PXEBC_OFFER_TYPE OfferType; UINT32 ProxyIndex; UINT32 SelectIndex; UINT32 Index; ASSERT (Private->SelectIndex > 0); SelectIndex = (UINT32) (Private->SelectIndex - 1); ASSERT (SelectIndex < PXEBC_OFFER_MAX_NUM); Cache6 = &Private->OfferBuffer[SelectIndex].Dhcp6; Status = EFI_SUCCESS; // // First try to cache DNS server address if DHCP6 offer provides. // if (Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER] != NULL) { Private->DnsServer = AllocateZeroPool (NTOHS (Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER]->OpLen)); if (Private->DnsServer == NULL) { return EFI_OUT_OF_RESOURCES; } CopyMem (Private->DnsServer, Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER]->Data, sizeof (EFI_IPv6_ADDRESS)); } if (Cache6->OfferType == PxeOfferTypeDhcpBinl) { // // DhcpBinl offer is selected, so need try to request bootfilename by this offer. // if (EFI_ERROR (PxeBcRetryDhcp6Binl (Private, SelectIndex))) { Status = EFI_NO_RESPONSE; } } else if (Cache6->OfferType == PxeOfferTypeDhcpOnly) { if (Private->IsProxyRecved) { // // DhcpOnly offer is selected, so need try to request bootfilename. // 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->OfferCount[Private->SelectProxyType] > 0); if (Private->SelectProxyType == PxeOfferTypeProxyBinl) { // // Try all the cached ProxyBinl offer one by one to request bootfilename. // for (Index = 0; Index < Private->OfferCount[Private->SelectProxyType]; Index++) { ProxyIndex = Private->OfferIndex[Private->SelectProxyType][Index]; if (!EFI_ERROR (PxeBcRetryDhcp6Binl (Private, ProxyIndex))) { break; } } if (Index == Private->OfferCount[Private->SelectProxyType]) { Status = EFI_NO_RESPONSE; } } else { // // For other proxy offers (pxe10 or wfm11a), 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++) { OfferType = Private->OfferBuffer[Index].Dhcp6.OfferType; if (!IS_PROXY_OFFER (OfferType)) { // // Skip non proxy dhcp offers. // continue; } if (OfferType == PxeOfferTypeProxyBinl) { // // Try all the cached ProxyBinl offer one by one to request bootfilename. // if (EFI_ERROR (PxeBcRetryDhcp6Binl (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 = PxeBcCopyDhcp6Proxy (Private, ProxyIndex); } } else { // // Otherwise, the bootfilename must be included in DhcpOnly offer. // ASSERT (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL); } } if (!EFI_ERROR (Status)) { // // All PXE boot information is ready by now. // Status = PxeBcCopyDhcp6Ack (Private, &Private->DhcpAck.Dhcp6.Packet.Ack, TRUE); Private->PxeBc.Mode->DhcpDiscoverValid = TRUE; } return Status; } /** Unregister the address by Ip6Config protocol. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. **/ VOID PxeBcUnregisterIp6Address ( IN PXEBC_PRIVATE_DATA *Private ) { if (Private->Ip6Policy != PXEBC_IP6_POLICY_MAX) { // // PXE driver change the policy of IP6 driver, it's a chance to recover. // Keep the point and there is no enough requirements to do recovery. // } } /** Check whether IP driver could route the message which will be sent to ServerIp address. This function will check the IP6 route table every 1 seconds until specified timeout is expired, if a valid route is found in IP6 route table, the address will be filed in GatewayAddr and return. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @param[in] TimeOutInSecond Timeout value in seconds. @param[out] GatewayAddr Pointer to store the gateway IP address. @retval EFI_SUCCESS Found a valid gateway address successfully. @retval EFI_TIMEOUT The operation is time out. @retval Other Unexpected error happened. **/ EFI_STATUS PxeBcCheckRouteTable ( IN PXEBC_PRIVATE_DATA *Private, IN UINTN TimeOutInSecond, OUT EFI_IPv6_ADDRESS *GatewayAddr ) { EFI_STATUS Status; EFI_IP6_PROTOCOL *Ip6; EFI_IP6_MODE_DATA Ip6ModeData; UINTN Index; EFI_EVENT TimeOutEvt; UINTN RetryCount; BOOLEAN GatewayIsFound; ASSERT (GatewayAddr != NULL); ASSERT (Private != NULL); Ip6 = Private->Ip6; GatewayIsFound = FALSE; RetryCount = 0; TimeOutEvt = NULL; ZeroMem (GatewayAddr, sizeof (EFI_IPv6_ADDRESS)); while (TRUE) { Status = Ip6->GetModeData (Ip6, &Ip6ModeData, NULL, NULL); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // Find out the gateway address which can route the message which send to ServerIp. // for (Index = 0; Index < Ip6ModeData.RouteCount; Index++) { if (NetIp6IsNetEqual (&Private->ServerIp.v6, &Ip6ModeData.RouteTable[Index].Destination, Ip6ModeData.RouteTable[Index].PrefixLength)) { IP6_COPY_ADDRESS (GatewayAddr, &Ip6ModeData.RouteTable[Index].Gateway); GatewayIsFound = TRUE; break; } } if (Ip6ModeData.AddressList != NULL) { FreePool (Ip6ModeData.AddressList); } if (Ip6ModeData.GroupTable != NULL) { FreePool (Ip6ModeData.GroupTable); } if (Ip6ModeData.RouteTable != NULL) { FreePool (Ip6ModeData.RouteTable); } if (Ip6ModeData.NeighborCache != NULL) { FreePool (Ip6ModeData.NeighborCache); } if (Ip6ModeData.PrefixTable != NULL) { FreePool (Ip6ModeData.PrefixTable); } if (Ip6ModeData.IcmpTypeList != NULL) { FreePool (Ip6ModeData.IcmpTypeList); } if (GatewayIsFound || RetryCount == TimeOutInSecond) { break; } RetryCount++; // // Delay 1 second then recheck it again. // if (TimeOutEvt == NULL) { Status = gBS->CreateEvent ( EVT_TIMER, TPL_CALLBACK, NULL, NULL, &TimeOutEvt ); if (EFI_ERROR (Status)) { goto ON_EXIT; } } Status = gBS->SetTimer (TimeOutEvt, TimerRelative, TICKS_PER_SECOND); if (EFI_ERROR (Status)) { goto ON_EXIT; } while (EFI_ERROR (gBS->CheckEvent (TimeOutEvt))) { Ip6->Poll (Ip6); } } ON_EXIT: if (TimeOutEvt != NULL) { gBS->CloseEvent (TimeOutEvt); } if (GatewayIsFound) { Status = EFI_SUCCESS; } else if (RetryCount == TimeOutInSecond) { Status = EFI_TIMEOUT; } return Status; } /** Register the ready station address and gateway by Ip6Config protocol. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @param[in] Address The pointer to the ready address. @retval EFI_SUCCESS Registered the address successfully. @retval Others Failed to register the address. **/ EFI_STATUS PxeBcRegisterIp6Address ( IN PXEBC_PRIVATE_DATA *Private, IN EFI_IPv6_ADDRESS *Address ) { EFI_IP6_PROTOCOL *Ip6; EFI_IP6_CONFIG_PROTOCOL *Ip6Cfg; EFI_IP6_CONFIG_POLICY Policy; EFI_IP6_CONFIG_MANUAL_ADDRESS CfgAddr; EFI_IPv6_ADDRESS GatewayAddr; UINTN DataSize; EFI_EVENT MappedEvt; EFI_STATUS Status; BOOLEAN NoGateway; EFI_IPv6_ADDRESS *Ip6Addr; UINTN Index; Status = EFI_SUCCESS; MappedEvt = NULL; Ip6Addr = NULL; DataSize = sizeof (EFI_IP6_CONFIG_POLICY); Ip6Cfg = Private->Ip6Cfg; Ip6 = Private->Ip6; NoGateway = FALSE; ZeroMem (&CfgAddr, sizeof (EFI_IP6_CONFIG_MANUAL_ADDRESS)); CopyMem (&CfgAddr.Address, Address, sizeof (EFI_IPv6_ADDRESS)); Status = Ip6->Configure (Ip6, &Private->Ip6CfgData); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // Retrieve the gateway address from IP6 route table. // Status = PxeBcCheckRouteTable (Private, PXEBC_IP6_ROUTE_TABLE_TIMEOUT, &GatewayAddr); if (EFI_ERROR (Status)) { NoGateway = TRUE; } // // There is no channel between IP6 and PXE driver about address setting, // so it has to set the new address by Ip6ConfigProtocol manually. // Policy = Ip6ConfigPolicyManual; Status = Ip6Cfg->SetData ( Ip6Cfg, Ip6ConfigDataTypePolicy, sizeof(EFI_IP6_CONFIG_POLICY), &Policy ); if (EFI_ERROR (Status)) { // // There is no need to recover later. // Private->Ip6Policy = PXEBC_IP6_POLICY_MAX; goto ON_EXIT; } // // Create a notify event to set address flag when DAD if IP6 driver succeeded. // Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, PxeBcCommonNotify, &Private->IsAddressOk, &MappedEvt ); if (EFI_ERROR (Status)) { goto ON_EXIT; } Private->IsAddressOk = FALSE; Status = Ip6Cfg->RegisterDataNotify ( Ip6Cfg, Ip6ConfigDataTypeManualAddress, MappedEvt ); if (EFI_ERROR(Status)) { goto ON_EXIT; } Status = Ip6Cfg->SetData ( Ip6Cfg, Ip6ConfigDataTypeManualAddress, sizeof(EFI_IP6_CONFIG_MANUAL_ADDRESS), &CfgAddr ); if (EFI_ERROR(Status) && Status != EFI_NOT_READY) { goto ON_EXIT; } else if (Status == EFI_NOT_READY) { // // Poll the network until the asynchronous process is finished. // while (!Private->IsAddressOk) { Ip6->Poll (Ip6); } // // Check whether the IP6 address setting is successed. // DataSize = 0; Status = Ip6Cfg->GetData ( Ip6Cfg, Ip6ConfigDataTypeManualAddress, &DataSize, NULL ); if (Status != EFI_BUFFER_TOO_SMALL || DataSize == 0) { Status = EFI_DEVICE_ERROR; goto ON_EXIT; } Ip6Addr = AllocatePool (DataSize); if (Ip6Addr == NULL) { return EFI_OUT_OF_RESOURCES; } Status = Ip6Cfg->GetData ( Ip6Cfg, Ip6ConfigDataTypeManualAddress, &DataSize, (VOID*) Ip6Addr ); if (EFI_ERROR (Status)) { Status = EFI_DEVICE_ERROR; goto ON_EXIT; } for (Index = 0; Index < DataSize / sizeof (EFI_IPv6_ADDRESS); Index++) { if (CompareMem (Ip6Addr + Index, Address, sizeof (EFI_IPv6_ADDRESS)) == 0) { break; } } if (Index == DataSize / sizeof (EFI_IPv6_ADDRESS)) { Status = EFI_ABORTED; goto ON_EXIT; } } // // Set the default gateway address back if needed. // if (!NoGateway && !NetIp6IsUnspecifiedAddr (&GatewayAddr)) { Status = Ip6Cfg->SetData ( Ip6Cfg, Ip6ConfigDataTypeGateway, sizeof (EFI_IPv6_ADDRESS), &GatewayAddr ); if (EFI_ERROR (Status)) { goto ON_EXIT; } } ON_EXIT: if (MappedEvt != NULL) { Ip6Cfg->UnregisterDataNotify ( Ip6Cfg, Ip6ConfigDataTypeManualAddress, MappedEvt ); gBS->CloseEvent (MappedEvt); } if (Ip6Addr != NULL) { FreePool (Ip6Addr); } return Status; } /** Set the IP6 policy to Automatic. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @retval EFI_SUCCESS Switch the IP policy successfully. @retval Others Unexpected error happened. **/ EFI_STATUS PxeBcSetIp6Policy ( IN PXEBC_PRIVATE_DATA *Private ) { EFI_IP6_CONFIG_POLICY Policy; EFI_STATUS Status; EFI_IP6_CONFIG_PROTOCOL *Ip6Cfg; UINTN DataSize; Ip6Cfg = Private->Ip6Cfg; DataSize = sizeof (EFI_IP6_CONFIG_POLICY); // // Get and store the current policy of IP6 driver. // Status = Ip6Cfg->GetData ( Ip6Cfg, Ip6ConfigDataTypePolicy, &DataSize, &Private->Ip6Policy ); if (EFI_ERROR (Status)) { return Status; } if (Private->Ip6Policy == Ip6ConfigPolicyManual) { Policy = Ip6ConfigPolicyAutomatic; Status = Ip6Cfg->SetData ( Ip6Cfg, Ip6ConfigDataTypePolicy, sizeof(EFI_IP6_CONFIG_POLICY), &Policy ); if (EFI_ERROR (Status)) { // // There is no need to recover later. // Private->Ip6Policy = PXEBC_IP6_POLICY_MAX; } } return Status; } /** This function will register the station IP address and flush IP instance to start using the new IP address. @param[in] Private The pointer to PXEBC_PRIVATE_DATA. @retval EFI_SUCCESS The new IP address has been configured successfully. @retval Others Failed to configure the address. **/ EFI_STATUS PxeBcSetIp6Address ( IN PXEBC_PRIVATE_DATA *Private ) { EFI_STATUS Status; EFI_DHCP6_PROTOCOL *Dhcp6; Dhcp6 = Private->Dhcp6; CopyMem (&Private->StationIp.v6, &Private->TmpStationIp.v6, sizeof (EFI_IPv6_ADDRESS)); CopyMem (&Private->PxeBc.Mode->StationIp.v6, &Private->StationIp.v6, sizeof (EFI_IPv6_ADDRESS)); Status = PxeBcRegisterIp6Address (Private, &Private->StationIp.v6); if (EFI_ERROR (Status)) { Dhcp6->Stop (Dhcp6); return Status; } Status = PxeBcFlushStationIp (Private, &Private->StationIp, NULL); if (EFI_ERROR (Status)) { PxeBcUnregisterIp6Address (Private); Dhcp6->Stop (Dhcp6); return Status; } AsciiPrint ("\n Station IP address is "); PxeBcShowIp6Addr (&Private->StationIp.v6); return EFI_SUCCESS; } /** EFI_DHCP6_CALLBACK is provided by the consumer of the EFI DHCPv6 Protocol driver to intercept events that occurred in the configuration process. @param[in] This The pointer to the EFI DHCPv6 Protocol. @param[in] Context The pointer to the context set by EFI_DHCP6_PROTOCOL.Configure(). @param[in] CurrentState The current operational state of the EFI DHCPv Protocol driver. @param[in] Dhcp6Event The event that occurs in the current state, which usually means a state transition. @param[in] Packet The DHCPv6 packet that is going to be sent or was already received. @param[out] NewPacket The packet that is used to replace the Packet above. @retval EFI_SUCCESS Told the EFI DHCPv6 Protocol driver to continue the DHCP process. @retval EFI_NOT_READY Only used in the Dhcp6Selecting state. The EFI DHCPv6 Protocol driver will continue to wait for more packets. @retval EFI_ABORTED Told the EFI DHCPv6 Protocol driver to abort the current process. **/ EFI_STATUS EFIAPI PxeBcDhcp6CallBack ( IN EFI_DHCP6_PROTOCOL *This, IN VOID *Context, IN EFI_DHCP6_STATE CurrentState, IN EFI_DHCP6_EVENT Dhcp6Event, IN EFI_DHCP6_PACKET *Packet, OUT EFI_DHCP6_PACKET **NewPacket OPTIONAL ) { PXEBC_PRIVATE_DATA *Private; EFI_PXE_BASE_CODE_MODE *Mode; EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL *Callback; EFI_DHCP6_PACKET *SelectAd; EFI_STATUS Status; BOOLEAN Received; if ((Dhcp6Event != Dhcp6RcvdAdvertise) && (Dhcp6Event != Dhcp6SelectAdvertise) && (Dhcp6Event != Dhcp6SendSolicit) && (Dhcp6Event != Dhcp6SendRequest) && (Dhcp6Event != Dhcp6RcvdReply)) { return EFI_SUCCESS; } ASSERT (Packet != NULL); Private = (PXEBC_PRIVATE_DATA *) Context; Mode = Private->PxeBc.Mode; Callback = Private->PxeBcCallback; // // Callback to user when any traffic occurred if has. // if (Dhcp6Event != Dhcp6SelectAdvertise && Callback != NULL) { Received = (BOOLEAN) (Dhcp6Event == Dhcp6RcvdAdvertise || Dhcp6Event == Dhcp6RcvdReply); Status = Callback->Callback ( Callback, Private->Function, Received, Packet->Length, (EFI_PXE_BASE_CODE_PACKET *) &Packet->Dhcp6 ); if (Status != EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE) { return EFI_ABORTED; } } Status = EFI_SUCCESS; switch (Dhcp6Event) { case Dhcp6SendSolicit: if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) { // // If the to be sent packet exceeds the maximum length, abort the DHCP process. // Status = EFI_ABORTED; break; } // // Record the first Solicate msg time // if (Private->SolicitTimes == 0) { CalcElapsedTime (Private); Private->SolicitTimes++; } // // Cache the dhcp discover packet to mode data directly. // CopyMem (&Mode->DhcpDiscover.Dhcpv4, &Packet->Dhcp6, Packet->Length); break; case Dhcp6RcvdAdvertise: Status = EFI_NOT_READY; if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) { // // Ignore the incoming packets which exceed the maximum length. // break; } if (Private->OfferNum < PXEBC_OFFER_MAX_NUM) { // // Cache the dhcp offers to OfferBuffer[] for select later, and record // the OfferIndex and OfferCount. // PxeBcCacheDhcp6Offer (Private, Packet); } break; case Dhcp6SendRequest: if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) { // // If the to be sent packet exceeds the maximum length, abort the DHCP process. // Status = EFI_ABORTED; break; } // // Store the request packet as seed packet for discover. // if (Private->Dhcp6Request != NULL) { FreePool (Private->Dhcp6Request); } Private->Dhcp6Request = AllocateZeroPool (Packet->Size); if (Private->Dhcp6Request != NULL) { CopyMem (Private->Dhcp6Request, Packet, Packet->Size); } break; case Dhcp6SelectAdvertise: // // Select offer by the default policy or by order, and record the SelectIndex // and SelectProxyType. // PxeBcSelectDhcp6Offer (Private); if (Private->SelectIndex == 0) { Status = EFI_ABORTED; } else { ASSERT (NewPacket != NULL); SelectAd = &Private->OfferBuffer[Private->SelectIndex - 1].Dhcp6.Packet.Offer; *NewPacket = AllocateZeroPool (SelectAd->Size); ASSERT (*NewPacket != NULL); if (*NewPacket == NULL) { return EFI_ABORTED; } CopyMem (*NewPacket, SelectAd, SelectAd->Size); } break; case Dhcp6RcvdReply: // // Cache the dhcp ack to Private->Dhcp6Ack, but it's not the final ack in mode data // without verification. // ASSERT (Private->SelectIndex != 0); Status = PxeBcCopyDhcp6Ack (Private, Packet, FALSE); if (EFI_ERROR (Status)) { Status = EFI_ABORTED; } break; default: ASSERT (0); } return Status; } /** Build and send out the request packet for the bootfile, and parse the reply. @param[in] Private The pointer to PxeBc private data. @param[in] Type PxeBc option boot item type. @param[in] Layer The pointer to option boot item layer. @param[in] UseBis Use BIS or not. @param[in] DestIp The pointer to the server address. @retval EFI_SUCCESS Successfully discovered the boot file. @retval EFI_OUT_OF_RESOURCES Failed to allocate resources. @retval EFI_NOT_FOUND Can't get the PXE reply packet. @retval Others Failed to discover the boot file. **/ EFI_STATUS PxeBcDhcp6Discover ( IN PXEBC_PRIVATE_DATA *Private, IN UINT16 Type, IN UINT16 *Layer, IN BOOLEAN UseBis, IN EFI_IP_ADDRESS *DestIp ) { EFI_PXE_BASE_CODE_UDP_PORT SrcPort; EFI_PXE_BASE_CODE_UDP_PORT DestPort; EFI_PXE_BASE_CODE_MODE *Mode; EFI_PXE_BASE_CODE_PROTOCOL *PxeBc; EFI_PXE_BASE_CODE_DHCPV6_PACKET *Discover; UINTN DiscoverLen; EFI_DHCP6_PACKET *Request; UINTN RequestLen; EFI_DHCP6_PACKET *Reply; UINT8 *RequestOpt; UINT8 *DiscoverOpt; UINTN ReadSize; UINT16 OpCode; UINT16 OpLen; UINT32 Xid; EFI_STATUS Status; PxeBc = &Private->PxeBc; Mode = PxeBc->Mode; Request = Private->Dhcp6Request; SrcPort = PXEBC_BS_DISCOVER_PORT; DestPort = PXEBC_BS_DISCOVER_PORT; if (!UseBis && Layer != NULL) { *Layer &= EFI_PXE_BASE_CODE_BOOT_LAYER_MASK; } if (Request == NULL) { return EFI_DEVICE_ERROR; } Discover = AllocateZeroPool (sizeof (EFI_PXE_BASE_CODE_DHCPV6_PACKET)); if (Discover == NULL) { return EFI_OUT_OF_RESOURCES; } // // Build the discover packet by the cached request packet before. // Xid = NET_RANDOM (NetRandomInitSeed ()); Discover->TransactionId = HTONL (Xid); Discover->MessageType = Request->Dhcp6.Header.MessageType; RequestOpt = Request->Dhcp6.Option; DiscoverOpt = Discover->DhcpOptions; DiscoverLen = sizeof (EFI_DHCP6_HEADER); RequestLen = DiscoverLen; while (RequestLen < Request->Length) { OpCode = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpCode); OpLen = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpLen); if (OpCode != EFI_DHCP6_IA_TYPE_NA && OpCode != EFI_DHCP6_IA_TYPE_TA) { // // Copy all the options except IA option. // CopyMem (DiscoverOpt, RequestOpt, OpLen + 4); DiscoverOpt += (OpLen + 4); DiscoverLen += (OpLen + 4); } RequestOpt += (OpLen + 4); RequestLen += (OpLen + 4); } Status = PxeBc->UdpWrite ( PxeBc, 0, &Private->ServerIp, &DestPort, NULL, &Private->StationIp, &SrcPort, NULL, NULL, &DiscoverLen, (VOID *) Discover ); if (EFI_ERROR (Status)) { goto ON_ERROR; } // // 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) { CopyMem (&Mode->PxeDiscover.Dhcpv6, Discover, DiscoverLen); Reply = &Private->PxeReply.Dhcp6.Packet.Ack; } else { Reply = &Private->ProxyOffer.Dhcp6.Packet.Offer; } ReadSize = (UINTN) Reply->Size; // // Start Udp6Read instance // Status = Private->Udp6Read->Configure (Private->Udp6Read, &Private->Udp6CfgData); if (EFI_ERROR (Status)) { goto ON_ERROR; } Status = PxeBc->UdpRead ( PxeBc, EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP, NULL, &SrcPort, &Private->ServerIp, &DestPort, NULL, NULL, &ReadSize, (VOID *) &Reply->Dhcp6 ); // // Stop Udp6Read instance // Private->Udp6Read->Configure (Private->Udp6Read, NULL); if (EFI_ERROR (Status)) { goto ON_ERROR; } return EFI_SUCCESS; ON_ERROR: if (Discover != NULL) { FreePool (Discover); } return Status; } /** Start the DHCPv6 S.A.R.R. process to acquire the IPv6 address and other PXE boot information. @param[in] Private The pointer to PxeBc private data. @param[in] Dhcp6 The pointer to the EFI_DHCP6_PROTOCOL @retval EFI_SUCCESS The S.A.R.R. process successfully finished. @retval Others Failed to finish the S.A.R.R. process. **/ EFI_STATUS PxeBcDhcp6Sarr ( IN PXEBC_PRIVATE_DATA *Private, IN EFI_DHCP6_PROTOCOL *Dhcp6 ) { EFI_PXE_BASE_CODE_MODE *PxeMode; EFI_DHCP6_CONFIG_DATA Config; EFI_DHCP6_MODE_DATA Mode; EFI_DHCP6_RETRANSMISSION *Retransmit; EFI_DHCP6_PACKET_OPTION *OptList[PXEBC_DHCP6_OPTION_MAX_NUM]; UINT8 Buffer[PXEBC_DHCP6_OPTION_MAX_SIZE]; UINT32 OptCount; EFI_STATUS Status; EFI_IP6_CONFIG_PROTOCOL *Ip6Cfg; EFI_STATUS TimerStatus; EFI_EVENT Timer; UINT64 GetMappingTimeOut; UINTN DataSize; EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS DadXmits; Status = EFI_SUCCESS; PxeMode = Private->PxeBc.Mode; Ip6Cfg = Private->Ip6Cfg; Timer = NULL; // // Build option list for the request packet. // OptCount = PxeBcBuildDhcp6Options (Private, OptList, Buffer); ASSERT (OptCount> 0); Retransmit = AllocateZeroPool (sizeof (EFI_DHCP6_RETRANSMISSION)); if (Retransmit == NULL) { return EFI_OUT_OF_RESOURCES; } ZeroMem (&Mode, sizeof (EFI_DHCP6_MODE_DATA)); ZeroMem (&Config, sizeof (EFI_DHCP6_CONFIG_DATA)); Config.OptionCount = OptCount; Config.OptionList = OptList; Config.Dhcp6Callback = PxeBcDhcp6CallBack; Config.CallbackContext = Private; Config.IaInfoEvent = NULL; Config.RapidCommit = FALSE; Config.ReconfigureAccept = FALSE; Config.IaDescriptor.IaId = Private->IaId; Config.IaDescriptor.Type = EFI_DHCP6_IA_TYPE_NA; Config.SolicitRetransmission = Retransmit; Retransmit->Irt = 4; Retransmit->Mrc = 4; Retransmit->Mrt = 32; Retransmit->Mrd = 60; // // Configure the DHCPv6 instance for PXE boot. // Status = Dhcp6->Configure (Dhcp6, &Config); FreePool (Retransmit); if (EFI_ERROR (Status)) { return Status; } // // Initialize the record fields for DHCPv6 offer in private data. // Private->IsProxyRecved = FALSE; Private->OfferNum = 0; Private->SelectIndex = 0; ZeroMem (Private->OfferCount, sizeof (Private->OfferCount)); ZeroMem (Private->OfferIndex, sizeof (Private->OfferIndex)); // // Start DHCPv6 S.A.R.R. process to acquire IPv6 address. // Status = Dhcp6->Start (Dhcp6); if (Status == EFI_NO_MAPPING) { // // IP6 Linklocal address is not available for use, so stop current Dhcp process // and wait for duplicate address detection to finish. // Dhcp6->Stop (Dhcp6); // // Get Duplicate Address Detection Transmits count. // DataSize = sizeof (EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS); Status = Ip6Cfg->GetData ( Ip6Cfg, Ip6ConfigDataTypeDupAddrDetectTransmits, &DataSize, &DadXmits ); if (EFI_ERROR (Status)) { Dhcp6->Configure (Dhcp6, NULL); return Status; } Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer); if (EFI_ERROR (Status)) { Dhcp6->Configure (Dhcp6, NULL); return Status; } GetMappingTimeOut = TICKS_PER_SECOND * DadXmits.DupAddrDetectTransmits + PXEBC_DAD_ADDITIONAL_DELAY; Status = gBS->SetTimer (Timer, TimerRelative, GetMappingTimeOut); if (EFI_ERROR (Status)) { gBS->CloseEvent (Timer); Dhcp6->Configure (Dhcp6, NULL); return Status; } do { TimerStatus = gBS->CheckEvent (Timer); if (!EFI_ERROR (TimerStatus)) { Status = Dhcp6->Start (Dhcp6); } } while (TimerStatus == EFI_NOT_READY); gBS->CloseEvent (Timer); } if (EFI_ERROR (Status)) { if (Status == EFI_ICMP_ERROR) { PxeMode->IcmpErrorReceived = TRUE; } Dhcp6->Configure (Dhcp6, NULL); return Status; } // // Get the acquired IPv6 address and store them. // Status = Dhcp6->GetModeData (Dhcp6, &Mode, NULL); if (EFI_ERROR (Status)) { Dhcp6->Stop (Dhcp6); return Status; } ASSERT ((Mode.Ia != NULL) && (Mode.Ia->State == Dhcp6Bound)); // // DHCP6 doesn't have an option to specify the router address on the subnet, the only way to get the // router address in IP6 is the router discovery mechanism (the RS and RA, which only be handled when // the IP policy is Automatic). So we just hold the station IP address here and leave the IP policy as // Automatic, until we get the server IP address. This could let IP6 driver finish the router discovery // to find a valid router address. // CopyMem (&Private->TmpStationIp.v6, &Mode.Ia->IaAddress[0].IpAddress, sizeof (EFI_IPv6_ADDRESS)); if (Mode.ClientId != NULL) { FreePool (Mode.ClientId); } if (Mode.Ia != NULL) { FreePool (Mode.Ia); } // // Check the selected offer whether BINL retry is needed. // Status = PxeBcHandleDhcp6Offer (Private); if (EFI_ERROR (Status)) { Dhcp6->Stop (Dhcp6); return Status; } return EFI_SUCCESS; }