/** @file
iSCSI DHCP6 related configuration routines.
Copyright (c) 2009 - 2017, 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 "IScsiImpl.h"
/**
Extract the Root Path option and get the required target information from
Boot File Uniform Resource Locator (URL) Option.
@param[in] RootPath The RootPath string.
@param[in] Length Length of the RootPath option payload.
@param[in, out] ConfigData The iSCSI session configuration data read from
nonvolatile device.
@retval EFI_SUCCESS All required information is extracted from the
RootPath option.
@retval EFI_NOT_FOUND The RootPath is not an iSCSI RootPath.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory.
@retval EFI_INVALID_PARAMETER The RootPath is malformatted.
**/
EFI_STATUS
IScsiDhcp6ExtractRootPath (
IN CHAR8 *RootPath,
IN UINT16 Length,
IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData
)
{
EFI_STATUS Status;
UINT16 IScsiRootPathIdLen;
CHAR8 *TmpStr;
ISCSI_ROOT_PATH_FIELD Fields[RP_FIELD_IDX_MAX];
ISCSI_ROOT_PATH_FIELD *Field;
UINT32 FieldIndex;
UINT8 Index;
ISCSI_SESSION_CONFIG_NVDATA *ConfigNvData;
EFI_IP_ADDRESS Ip;
UINT8 IpMode;
ConfigNvData = &ConfigData->SessionConfigData;
ConfigNvData->DnsMode = FALSE;
//
// "iscsi:"":"":"":"":"
//
IScsiRootPathIdLen = (UINT16) AsciiStrLen (ISCSI_ROOT_PATH_ID);
if ((Length <= IScsiRootPathIdLen) ||
(CompareMem (RootPath, ISCSI_ROOT_PATH_ID, IScsiRootPathIdLen) != 0)) {
return EFI_NOT_FOUND;
}
//
// Skip the iSCSI RootPath ID "iscsi:".
//
RootPath = RootPath + IScsiRootPathIdLen;
Length = (UINT16) (Length - IScsiRootPathIdLen);
TmpStr = (CHAR8 *) AllocatePool (Length + 1);
if (TmpStr == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (TmpStr, RootPath, Length);
TmpStr[Length] = '\0';
Index = 0;
FieldIndex = 0;
ZeroMem (&Fields[0], sizeof (Fields));
//
// Extract SERVERNAME field in the Root Path option.
//
if (TmpStr[Index] != ISCSI_ROOT_PATH_ADDR_START_DELIMITER) {
//
// The servername is expressed as domain name.
//
ConfigNvData->DnsMode = TRUE;
} else {
Index++;
}
Fields[RP_FIELD_IDX_SERVERNAME].Str = &TmpStr[Index];
if (!ConfigNvData->DnsMode) {
while ((TmpStr[Index] != ISCSI_ROOT_PATH_ADDR_END_DELIMITER)&& (Index < Length)) {
Index++;
}
//
// Skip ']' and ':'.
//
TmpStr[Index] = '\0';
Index += 2;
} else {
while ((TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) && (Index < Length)) {
Index++;
}
//
// Skip ':'.
//
TmpStr[Index] = '\0';
Index += 1;
}
Fields[RP_FIELD_IDX_SERVERNAME].Len = (UINT8) AsciiStrLen (Fields[RP_FIELD_IDX_SERVERNAME].Str);
//
// Extract others fields in the Root Path option string.
//
for (FieldIndex = 1; (FieldIndex < RP_FIELD_IDX_MAX) && (Index < Length); FieldIndex++) {
if (TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) {
Fields[FieldIndex].Str = &TmpStr[Index];
}
while ((TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) && (Index < Length)) {
Index++;
}
if (TmpStr[Index] == ISCSI_ROOT_PATH_FIELD_DELIMITER) {
if (FieldIndex != RP_FIELD_IDX_TARGETNAME) {
TmpStr[Index] = '\0';
Index++;
}
if (Fields[FieldIndex].Str != NULL) {
Fields[FieldIndex].Len = (UINT8) AsciiStrLen (Fields[FieldIndex].Str);
}
}
}
if (FieldIndex != RP_FIELD_IDX_MAX) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
if ((Fields[RP_FIELD_IDX_SERVERNAME].Str == NULL) ||
(Fields[RP_FIELD_IDX_TARGETNAME].Str == NULL) ||
(Fields[RP_FIELD_IDX_PROTOCOL].Len > 1)
) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
//
// Get the IP address of the target.
//
Field = &Fields[RP_FIELD_IDX_SERVERNAME];
if (ConfigNvData->IpMode < IP_MODE_AUTOCONFIG) {
IpMode = ConfigNvData->IpMode;
} else {
IpMode = ConfigData->AutoConfigureMode;
}
//
// Server name is expressed as domain name, just save it.
//
if (ConfigNvData->DnsMode) {
if (Field->Len > sizeof (ConfigNvData->TargetUrl)) {
return EFI_INVALID_PARAMETER;
}
CopyMem (&ConfigNvData->TargetUrl, Field->Str, Field->Len);
ConfigNvData->TargetUrl[Field->Len + 1] = '\0';
} else {
ZeroMem(&ConfigNvData->TargetUrl, sizeof (ConfigNvData->TargetUrl));
Status = IScsiAsciiStrToIp (Field->Str, IpMode, &Ip);
CopyMem (&ConfigNvData->TargetIp, &Ip, sizeof (EFI_IP_ADDRESS));
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
}
//
// Check the protocol type.
//
Field = &Fields[RP_FIELD_IDX_PROTOCOL];
if ((Field->Str != NULL) && ((*(Field->Str) - '0') != EFI_IP_PROTO_TCP)) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
//
// Get the port of the iSCSI target.
//
Field = &Fields[RP_FIELD_IDX_PORT];
if (Field->Str != NULL) {
ConfigNvData->TargetPort = (UINT16) AsciiStrDecimalToUintn (Field->Str);
} else {
ConfigNvData->TargetPort = ISCSI_WELL_KNOWN_PORT;
}
//
// Get the LUN.
//
Field = &Fields[RP_FIELD_IDX_LUN];
if (Field->Str != NULL) {
Status = IScsiAsciiStrToLun (Field->Str, ConfigNvData->BootLun);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
} else {
ZeroMem (ConfigNvData->BootLun, sizeof (ConfigNvData->BootLun));
}
//
// Get the target iSCSI Name.
//
Field = &Fields[RP_FIELD_IDX_TARGETNAME];
if (AsciiStrLen (Field->Str) > ISCSI_NAME_MAX_SIZE - 1) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
//
// Validate the iSCSI name.
//
Status = IScsiNormalizeName (Field->Str, AsciiStrLen (Field->Str));
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
AsciiStrCpyS (ConfigNvData->TargetName, ISCSI_NAME_MAX_SIZE, Field->Str);
ON_EXIT:
FreePool (TmpStr);
return Status;
}
/**
EFI_DHCP6_INFO_CALLBACK is provided by the consumer of the EFI DHCPv6 Protocol
instance to intercept events that occurs in the DHCPv6 Information Request
exchange process.
@param[in] This Pointer to the EFI_DHCP6_PROTOCOL instance that
is used to configure this callback function.
@param[in] Context Pointer to the context that is initialized in
the EFI_DHCP6_PROTOCOL.InfoRequest().
@param[in] Packet Pointer to Reply packet that has been received.
The EFI DHCPv6 Protocol instance is responsible
for freeing the buffer.
@retval EFI_SUCCESS Tell the EFI DHCPv6 Protocol instance to finish
Information Request exchange process.
@retval EFI_NOT_READY Tell the EFI DHCPv6 Protocol instance to continue
Information Request exchange process.
@retval EFI_ABORTED Tell the EFI DHCPv6 Protocol instance to abort
the Information Request exchange process.
@retval EFI_UNSUPPORTED Tell the EFI DHCPv6 Protocol instance to finish
the Information Request exchange process because some
request information are not received.
**/
EFI_STATUS
EFIAPI
IScsiDhcp6ParseReply (
IN EFI_DHCP6_PROTOCOL *This,
IN VOID *Context,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
UINT32 Index;
UINT32 OptionCount;
EFI_DHCP6_PACKET_OPTION *BootFileOpt;
EFI_DHCP6_PACKET_OPTION **OptionList;
ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData;
UINT16 ParaLen;
OptionCount = 0;
BootFileOpt = NULL;
Status = This->Parse (This, Packet, &OptionCount, NULL);
if (Status != EFI_BUFFER_TOO_SMALL) {
return EFI_NOT_READY;
}
OptionList = AllocateZeroPool (OptionCount * sizeof (EFI_DHCP6_PACKET_OPTION *));
if (OptionList == NULL) {
return EFI_NOT_READY;
}
Status = This->Parse (This, Packet, &OptionCount, OptionList);
if (EFI_ERROR (Status)) {
Status = EFI_NOT_READY;
goto Exit;
}
ConfigData = (ISCSI_ATTEMPT_CONFIG_NVDATA *) Context;
for (Index = 0; Index < OptionCount; Index++) {
OptionList[Index]->OpCode = NTOHS (OptionList[Index]->OpCode);
OptionList[Index]->OpLen = NTOHS (OptionList[Index]->OpLen);
//
// Get DNS server addresses from this reply packet.
//
if (OptionList[Index]->OpCode == DHCP6_OPT_DNS_SERVERS) {
if (((OptionList[Index]->OpLen & 0xf) != 0) || (OptionList[Index]->OpLen == 0)) {
Status = EFI_UNSUPPORTED;
goto Exit;
}
//
// Primary DNS server address.
//
CopyMem (&ConfigData->PrimaryDns, &OptionList[Index]->Data[0], sizeof (EFI_IPv6_ADDRESS));
if (OptionList[Index]->OpLen > 16) {
//
// Secondary DNS server address
//
CopyMem (&ConfigData->SecondaryDns, &OptionList[Index]->Data[16], sizeof (EFI_IPv6_ADDRESS));
}
} else if (OptionList[Index]->OpCode == DHCP6_OPT_BOOT_FILE_URL) {
//
// The server sends this option to inform the client about an URL to a boot file.
//
BootFileOpt = OptionList[Index];
} else if (OptionList[Index]->OpCode == DHCP6_OPT_BOOT_FILE_PARAM) {
//
// The server sends this option to inform the client about DHCP6 server address.
//
if (OptionList[Index]->OpLen < 18) {
Status = EFI_UNSUPPORTED;
goto Exit;
}
//
// Check param-len 1, should be 16 bytes.
//
CopyMem (&ParaLen, &OptionList[Index]->Data[0], sizeof (UINT16));
if (NTOHS (ParaLen) != 16) {
Status = EFI_UNSUPPORTED;
goto Exit;
}
CopyMem (&ConfigData->DhcpServer, &OptionList[Index]->Data[2], sizeof (EFI_IPv6_ADDRESS));
}
}
if (BootFileOpt == NULL) {
Status = EFI_UNSUPPORTED;
goto Exit;
}
//
// Get iSCSI root path from Boot File Uniform Resource Locator (URL) Option
//
Status = IScsiDhcp6ExtractRootPath (
(CHAR8 *) BootFileOpt->Data,
BootFileOpt->OpLen,
ConfigData
);
Exit:
FreePool (OptionList);
return Status;
}
/**
Parse the DHCP ACK to get the address configuration and DNS information.
@param[in] Image The handle of the driver image.
@param[in] Controller The handle of the controller;
@param[in, out] ConfigData The attempt configuration data.
@retval EFI_SUCCESS The DNS information is got from the DHCP ACK.
@retval EFI_NO_MAPPING DHCP failed to acquire address and other
information.
@retval EFI_INVALID_PARAMETER The DHCP ACK's DNS option is malformatted.
@retval EFI_DEVICE_ERROR Some unexpected error occurred.
@retval EFI_OUT_OF_RESOURCES There is no sufficient resource to finish the
operation.
@retval EFI_NO_MEDIA There was a media error.
**/
EFI_STATUS
IScsiDoDhcp6 (
IN EFI_HANDLE Image,
IN EFI_HANDLE Controller,
IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData
)
{
EFI_HANDLE Dhcp6Handle;
EFI_DHCP6_PROTOCOL *Dhcp6;
EFI_STATUS Status;
EFI_STATUS TimerStatus;
EFI_DHCP6_PACKET_OPTION *Oro;
EFI_DHCP6_RETRANSMISSION InfoReqReXmit;
EFI_EVENT Timer;
BOOLEAN MediaPresent;
//
// Check media status before doing DHCP.
//
MediaPresent = TRUE;
NetLibDetectMedia (Controller, &MediaPresent);
if (!MediaPresent) {
return EFI_NO_MEDIA;
}
//
// iSCSI will only request target info from DHCPv6 server.
//
if (!ConfigData->SessionConfigData.TargetInfoFromDhcp) {
return EFI_SUCCESS;
}
Dhcp6Handle = NULL;
Dhcp6 = NULL;
Oro = NULL;
Timer = NULL;
//
// Create a DHCP6 child instance and get the protocol.
//
Status = NetLibCreateServiceChild (
Controller,
Image,
&gEfiDhcp6ServiceBindingProtocolGuid,
&Dhcp6Handle
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->OpenProtocol (
Dhcp6Handle,
&gEfiDhcp6ProtocolGuid,
(VOID **) &Dhcp6,
Image,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Oro = AllocateZeroPool (sizeof (EFI_DHCP6_PACKET_OPTION) + 5);
if (Oro == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
//
// Ask the server to reply with DNS and Boot File URL options by info request.
// All members in EFI_DHCP6_PACKET_OPTION are in network order.
//
Oro->OpCode = HTONS (DHCP6_OPT_ORO);
Oro->OpLen = HTONS (2 * 3);
Oro->Data[1] = DHCP6_OPT_DNS_SERVERS;
Oro->Data[3] = DHCP6_OPT_BOOT_FILE_URL;
Oro->Data[5] = DHCP6_OPT_BOOT_FILE_PARAM;
InfoReqReXmit.Irt = 4;
InfoReqReXmit.Mrc = 1;
InfoReqReXmit.Mrt = 10;
InfoReqReXmit.Mrd = 30;
Status = Dhcp6->InfoRequest (
Dhcp6,
TRUE,
Oro,
0,
NULL,
&InfoReqReXmit,
NULL,
IScsiDhcp6ParseReply,
ConfigData
);
if (Status == EFI_NO_MAPPING) {
Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Status = gBS->SetTimer (
Timer,
TimerRelative,
ISCSI_GET_MAPPING_TIMEOUT
);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
do {
TimerStatus = gBS->CheckEvent (Timer);
if (!EFI_ERROR (TimerStatus)) {
Status = Dhcp6->InfoRequest (
Dhcp6,
TRUE,
Oro,
0,
NULL,
&InfoReqReXmit,
NULL,
IScsiDhcp6ParseReply,
ConfigData
);
}
} while (TimerStatus == EFI_NOT_READY);
}
ON_EXIT:
if (Oro != NULL) {
FreePool (Oro);
}
if (Timer != NULL) {
gBS->CloseEvent (Timer);
}
if (Dhcp6 != NULL) {
gBS->CloseProtocol (
Dhcp6Handle,
&gEfiDhcp6ProtocolGuid,
Image,
Controller
);
}
NetLibDestroyServiceChild (
Controller,
Image,
&gEfiDhcp6ServiceBindingProtocolGuid,
Dhcp6Handle
);
return Status;
}