/** @file
Functions implementation related with DHCPv6 for UefiPxeBc Driver.
(C) Copyright 2014 Hewlett-Packard Development Company, L.P.
Copyright (c) 2009 - 2016, Intel Corporation. All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php.
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#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 postion 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 (4);
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);
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.
**/
VOID
PxeBcCacheDhcp6Packet (
IN EFI_DHCP6_PACKET *Dst,
IN EFI_DHCP6_PACKET *Src
)
{
ASSERT (Dst->Size >= Src->Length);
CopyMem (&Dst->Dhcp6, &Src->Dhcp6, Src->Length);
Dst->Length = Src->Length;
}
/**
Free all the nodes in the list for boot file.
@param[in] Head The pointer to the head of list.
**/
VOID
PxeBcFreeBootFileOption (
IN LIST_ENTRY *Head
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
PXEBC_DHCP6_OPTION_NODE *Node;
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, Head) {
Node = NET_LIST_USER_STRUCT (Entry, PXEBC_DHCP6_OPTION_NODE, Link);
RemoveEntryList (Entry);
FreePool (Node);
}
}
/**
Parse the Boot File URL option.
@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 keybroad has not finish.
**/
EFI_STATUS
PxeBcExtractBootFileUrl (
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;
EFI_STATUS Status;
//
// 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.3 Errata D specification, bootfile-url format
// is tftp://[SERVER_ADDRESS]/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) {
FreePool (TmpStr);
return EFI_INVALID_PARAMETER;
}
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;
}
//
// Get the part of BOOTFILE_NAME string.
//
BootFileNamePtr = (CHAR8*)((UINTN)ServerAddress + 1);
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) {
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;
}
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, embeded 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.
**/
VOID
PxeBcCopyDhcp6Ack (
IN PXEBC_PRIVATE_DATA *Private,
IN EFI_DHCP6_PACKET *Ack,
IN BOOLEAN Verified
)
{
EFI_PXE_BASE_CODE_MODE *Mode;
Mode = Private->PxeBc.Mode;
PxeBcCacheDhcp6Packet (&Private->DhcpAck.Dhcp6.Packet.Ack, Ack);
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;
}
}
/**
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.
**/
VOID
PxeBcCopyDhcp6Proxy (
IN PXEBC_PRIVATE_DATA *Private,
IN UINT32 OfferIndex
)
{
EFI_PXE_BASE_CODE_MODE *Mode;
EFI_DHCP6_PACKET *Offer;
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.
//
PxeBcCacheDhcp6Packet (&Private->ProxyOffer.Dhcp6.Packet.Offer, Offer);
PxeBcParseDhcp6Packet (&Private->ProxyOffer.Dhcp6);
//
// Store this packet into mode data.
//
CopyMem (&Mode->ProxyOffer.Dhcpv6, &Offer->Dhcp6, Offer->Length);
Mode->ProxyOfferReceived = TRUE;
}
/**
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 *ProxyOffer;
UINT8 *Option;
PxeBc = &Private->PxeBc;
Request = Private->Dhcp6Request;
ProxyOffer = &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 = ProxyOffer->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.
//
Option = PxeBcDhcp6SeekOption (
ProxyOffer->Dhcp6.Option,
ProxyOffer->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)) {
return Status;
}
//
// 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)) {
return Status;
}
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)) {
return Status;
}
//
// Update length
//
Reply->Length = (UINT32) ReadSize;
return EFI_SUCCESS;
}
/**
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->OfferType == PxeOfferTypeDhcpBinl) {
//
// 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->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.
**/
VOID
PxeBcCacheDhcp6Offer (
IN PXEBC_PRIVATE_DATA *Private,
IN EFI_DHCP6_PACKET *RcvdOffer
)
{
PXEBC_DHCP6_PACKET_CACHE *Cache6;
EFI_DHCP6_PACKET *Offer;
PXEBC_OFFER_TYPE OfferType;
Cache6 = &Private->OfferBuffer[Private->OfferNum].Dhcp6;
Offer = &Cache6->Packet.Offer;
//
// Cache the content of DHCPv6 packet firstly.
//
PxeBcCacheDhcp6Packet (Offer, RcvdOffer);
//
// Validate the DHCPv6 packet, and parse the options and offer type.
//
if (EFI_ERROR (PxeBcParseDhcp6Packet (Cache6))) {
return ;
}
//
// 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 (Private->OfferCount[OfferType] > 0) {
//
// Only cache the first PXE10/WFM11a offer, and discard the others.
//
Private->OfferIndex[OfferType][0] = Private->OfferNum;
Private->OfferCount[OfferType] = 1;
} else {
return;
}
} 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++;
}
/**
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.
**/
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;
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.
//
PxeBcCopyDhcp6Proxy (Private, ProxyIndex);
}
} else {
//
// Othewise, 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.
//
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 Unexpect 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 succesfully.
@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 succesfully.
@retval Others Unexpect 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 ocurred 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:
//
// 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 (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:
//
// 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);
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);
PxeBcCopyDhcp6Ack (Private, Packet, FALSE);
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)) {
return Status;
}
//
// 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)) {
return Status;
}
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)) {
return Status;
}
return EFI_SUCCESS;
}
/**
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;
}