diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/NetworkPkg/IScsiDxe/IScsiDhcp.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/NetworkPkg/IScsiDxe/IScsiDhcp.c | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/NetworkPkg/IScsiDxe/IScsiDhcp.c b/src/VBox/Devices/EFI/Firmware/NetworkPkg/IScsiDxe/IScsiDhcp.c new file mode 100644 index 00000000..94ea81d6 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/NetworkPkg/IScsiDxe/IScsiDhcp.c @@ -0,0 +1,568 @@ +/** @file + iSCSI DHCP4 related configuration routines. + +Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "IScsiImpl.h" + + +/** + Extract the Root Path option and get the required target information. + + @param[in] RootPath The RootPath. + @param[in] Length Length of the RootPath option payload. + @param[in, out] ConfigData The iSCSI attempt configuration data read + from a 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 +IScsiDhcpExtractRootPath ( + IN CHAR8 *RootPath, + IN UINT8 Length, + IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData + ) +{ + EFI_STATUS Status; + UINT8 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; + + // + // "iscsi:"<servername>":"<protocol>":"<port>":"<LUN>":"<targetname> + // + IScsiRootPathIdLen = (UINT8) 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 += IScsiRootPathIdLen; + Length = (UINT8) (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 = RP_FIELD_IDX_SERVERNAME; + ZeroMem (&Fields[0], sizeof (Fields)); + + // + // Extract the fields in the Root Path option string. + // + for (FieldIndex = RP_FIELD_IDX_SERVERNAME; (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 ((!NET_IS_DIGIT (*(Field->Str))) && (*(Field->Str) != '[')) { + ConfigNvData->DnsMode = TRUE; + if ((Field->Len + 2) > sizeof (ConfigNvData->TargetUrl)) { + return EFI_INVALID_PARAMETER; + } + CopyMem (&ConfigNvData->TargetUrl, Field->Str, Field->Len); + ConfigNvData->TargetUrl[Field->Len + 1] = '\0'; + } else { + ConfigNvData->DnsMode = FALSE; + 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; +} + +/** + The callback function registered to the DHCP4 instance that is used to select + the qualified DHCP OFFER. + + @param[in] This The DHCP4 protocol. + @param[in] Context The context set when configuring the DHCP4 protocol. + @param[in] CurrentState The current state of the DHCP4 protocol. + @param[in] Dhcp4Event The event occurs in the current state. + @param[in] Packet The DHCP packet that is to be sent or was already received. + @param[out] NewPacket The packet used to replace the above Packet. + + @retval EFI_SUCCESS Either the DHCP OFFER is qualified or we're not intereseted + in the Dhcp4Event. + @retval EFI_NOT_READY The DHCP OFFER packet doesn't match our requirements. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +EFIAPI +IScsiDhcpSelectOffer ( + IN EFI_DHCP4_PROTOCOL *This, + IN VOID *Context, + IN EFI_DHCP4_STATE CurrentState, + IN EFI_DHCP4_EVENT Dhcp4Event, + IN EFI_DHCP4_PACKET *Packet, OPTIONAL + OUT EFI_DHCP4_PACKET **NewPacket OPTIONAL + ) +{ + EFI_STATUS Status; + UINT32 OptionCount; + EFI_DHCP4_PACKET_OPTION **OptionList; + UINT32 Index; + + if ((Dhcp4Event != Dhcp4RcvdOffer) && (Dhcp4Event != Dhcp4SelectOffer)) { + return EFI_SUCCESS; + } + + OptionCount = 0; + + Status = This->Parse (This, Packet, &OptionCount, NULL); + if (Status != EFI_BUFFER_TOO_SMALL) { + return EFI_NOT_READY; + } + + OptionList = AllocatePool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *)); + if (OptionList == NULL) { + return EFI_NOT_READY; + } + + Status = This->Parse (This, Packet, &OptionCount, OptionList); + if (EFI_ERROR (Status)) { + FreePool (OptionList); + return EFI_NOT_READY; + } + + for (Index = 0; Index < OptionCount; Index++) { + if (OptionList[Index]->OpCode != DHCP4_TAG_ROOTPATH) { + continue; + } + + Status = IScsiDhcpExtractRootPath ( + (CHAR8 *) &OptionList[Index]->Data[0], + OptionList[Index]->Length, + (ISCSI_ATTEMPT_CONFIG_NVDATA *) Context + ); + + break; + } + + if (Index == OptionCount) { + Status = EFI_NOT_READY; + } + + FreePool (OptionList); + + return Status; +} + +/** + Parse the DHCP ACK to get the address configuration and DNS information. + + @param[in] Dhcp4 The DHCP4 protocol. + @param[in, out] ConfigData The session 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 Other errors as indicated. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + +**/ +EFI_STATUS +IScsiParseDhcpAck ( + IN EFI_DHCP4_PROTOCOL *Dhcp4, + IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData + ) +{ + EFI_STATUS Status; + EFI_DHCP4_MODE_DATA Dhcp4ModeData; + UINT32 OptionCount; + EFI_DHCP4_PACKET_OPTION **OptionList; + UINT32 Index; + ISCSI_SESSION_CONFIG_NVDATA *NvData; + + Status = Dhcp4->GetModeData (Dhcp4, &Dhcp4ModeData); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Dhcp4ModeData.State != Dhcp4Bound) { + return EFI_NO_MAPPING; + } + + NvData = &ConfigData->SessionConfigData; + + CopyMem (&NvData->LocalIp, &Dhcp4ModeData.ClientAddress, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&NvData->SubnetMask, &Dhcp4ModeData.SubnetMask, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&NvData->Gateway, &Dhcp4ModeData.RouterAddress, sizeof (EFI_IPv4_ADDRESS)); + + OptionCount = 0; + OptionList = NULL; + + Status = Dhcp4->Parse (Dhcp4, Dhcp4ModeData.ReplyPacket, &OptionCount, OptionList); + if (Status != EFI_BUFFER_TOO_SMALL) { + return EFI_DEVICE_ERROR; + } + + OptionList = AllocatePool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *)); + if (OptionList == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = Dhcp4->Parse (Dhcp4, Dhcp4ModeData.ReplyPacket, &OptionCount, OptionList); + if (EFI_ERROR (Status)) { + FreePool (OptionList); + return EFI_DEVICE_ERROR; + } + + for (Index = 0; Index < OptionCount; Index++) { + // + // Get DNS server addresses and DHCP server address from this offer. + // + if (OptionList[Index]->OpCode == DHCP4_TAG_DNS_SERVER) { + + if (((OptionList[Index]->Length & 0x3) != 0) || (OptionList[Index]->Length == 0)) { + Status = EFI_INVALID_PARAMETER; + break; + } + // + // Primary DNS server address. + // + CopyMem (&ConfigData->PrimaryDns, &OptionList[Index]->Data[0], sizeof (EFI_IPv4_ADDRESS)); + + if (OptionList[Index]->Length > 4) { + // + // Secondary DNS server address. + // + CopyMem (&ConfigData->SecondaryDns, &OptionList[Index]->Data[4], sizeof (EFI_IPv4_ADDRESS)); + } + } else if (OptionList[Index]->OpCode == DHCP4_TAG_SERVER_ID) { + if (OptionList[Index]->Length != 4) { + Status = EFI_INVALID_PARAMETER; + break; + } + + CopyMem (&ConfigData->DhcpServer, &OptionList[Index]->Data[0], sizeof (EFI_IPv4_ADDRESS)); + } + } + + FreePool (OptionList); + + return Status; +} + +/** + This function will switch the IP4 configuration policy to Static. + + @param[in] Ip4Config2 Pointer to the IP4 configuration protocol. + + @retval EFI_SUCCESS The policy is already configured to static. + @retval Others Other error as indicated. + +**/ +EFI_STATUS +IScsiSetIp4Policy ( + IN EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2 + ) +{ + EFI_IP4_CONFIG2_POLICY Policy; + EFI_STATUS Status; + UINTN DataSize; + + DataSize = sizeof (EFI_IP4_CONFIG2_POLICY); + Status = Ip4Config2->GetData ( + Ip4Config2, + Ip4Config2DataTypePolicy, + &DataSize, + &Policy + ); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Policy != Ip4Config2PolicyStatic) { + Policy = Ip4Config2PolicyStatic; + Status= Ip4Config2->SetData ( + Ip4Config2, + Ip4Config2DataTypePolicy, + sizeof (EFI_IP4_CONFIG2_POLICY), + &Policy + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +/** + 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_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_NO_MEDIA There was a media error. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiDoDhcp ( + IN EFI_HANDLE Image, + IN EFI_HANDLE Controller, + IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData + ) +{ + EFI_HANDLE Dhcp4Handle; + EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2; + EFI_DHCP4_PROTOCOL *Dhcp4; + EFI_STATUS Status; + EFI_DHCP4_PACKET_OPTION *ParaList; + EFI_DHCP4_CONFIG_DATA Dhcp4ConfigData; + ISCSI_SESSION_CONFIG_NVDATA *NvData; + EFI_STATUS MediaStatus; + + Dhcp4Handle = NULL; + Ip4Config2 = NULL; + Dhcp4 = NULL; + ParaList = NULL; + + // + // Check media status before doing DHCP. + // + MediaStatus = EFI_SUCCESS; + NetLibDetectMediaWaitTimeout (Controller, ISCSI_CHECK_MEDIA_GET_DHCP_WAITING_TIME, &MediaStatus); + if (MediaStatus!= EFI_SUCCESS) { + AsciiPrint ("\n Error: Could not detect network connection.\n"); + return EFI_NO_MEDIA; + } + + // + // DHCP4 service allows only one of its children to be configured in + // the active state, If the DHCP4 D.O.R.A started by IP4 auto + // configuration and has not been completed, the Dhcp4 state machine + // will not be in the right state for the iSCSI to start a new round D.O.R.A. + // So, we need to switch its policy to static. + // + Status = gBS->HandleProtocol (Controller, &gEfiIp4Config2ProtocolGuid, (VOID **) &Ip4Config2); + if (!EFI_ERROR (Status)) { + Status = IScsiSetIp4Policy (Ip4Config2); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Create a DHCP4 child instance and get the protocol. + // + Status = NetLibCreateServiceChild ( + Controller, + Image, + &gEfiDhcp4ServiceBindingProtocolGuid, + &Dhcp4Handle + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->OpenProtocol ( + Dhcp4Handle, + &gEfiDhcp4ProtocolGuid, + (VOID **) &Dhcp4, + Image, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + NvData = &ConfigData->SessionConfigData; + + ParaList = AllocatePool (sizeof (EFI_DHCP4_PACKET_OPTION) + 3); + if (ParaList == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + // + // Ask the server to reply with Netmask, Router, DNS, and RootPath options. + // + ParaList->OpCode = DHCP4_TAG_PARA_LIST; + ParaList->Length = (UINT8) (NvData->TargetInfoFromDhcp ? 4 : 3); + ParaList->Data[0] = DHCP4_TAG_NETMASK; + ParaList->Data[1] = DHCP4_TAG_ROUTER; + ParaList->Data[2] = DHCP4_TAG_DNS_SERVER; + ParaList->Data[3] = DHCP4_TAG_ROOTPATH; + + ZeroMem (&Dhcp4ConfigData, sizeof (EFI_DHCP4_CONFIG_DATA)); + Dhcp4ConfigData.OptionCount = 1; + Dhcp4ConfigData.OptionList = &ParaList; + + if (NvData->TargetInfoFromDhcp) { + // + // Use callback to select an offer that contains target information. + // + Dhcp4ConfigData.Dhcp4Callback = IScsiDhcpSelectOffer; + Dhcp4ConfigData.CallbackContext = ConfigData; + } + + Status = Dhcp4->Configure (Dhcp4, &Dhcp4ConfigData); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + Status = Dhcp4->Start (Dhcp4, NULL); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + // + // Parse the ACK to get required information. + // + Status = IScsiParseDhcpAck (Dhcp4, ConfigData); + +ON_EXIT: + + if (ParaList != NULL) { + FreePool (ParaList); + } + + if (Dhcp4 != NULL) { + Dhcp4->Stop (Dhcp4); + Dhcp4->Configure (Dhcp4, NULL); + + gBS->CloseProtocol ( + Dhcp4Handle, + &gEfiDhcp4ProtocolGuid, + Image, + Controller + ); + } + + NetLibDestroyServiceChild ( + Controller, + Image, + &gEfiDhcp4ServiceBindingProtocolGuid, + Dhcp4Handle + ); + + return Status; +} |