/** ISci DHCP related configuration routines. Copyright (c) 2004 - 2007, 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. Module Name: IScsiDhcp.c Abstract: IScsi DHCP related configuration routines. **/ #include "IScsiImpl.h" /** Extract the Root Path option and get the required target information. @param RootPath[in] The RootPath. @param Length[in] Length of the RootPath option payload. @param ConfigNvData[in] 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 mal-formatted. **/ STATIC EFI_STATUS IScsiDhcpExtractRootPath ( IN CHAR8 *RootPath, IN UINT8 Length, IN ISCSI_SESSION_CONFIG_NVDATA *ConfigNvData ) { 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:"":"":"":"":" // 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 = 0; ZeroMem (&Fields[0], sizeof (Fields)); // // Extract the fields in the Root Path option string. // for (FieldIndex = 0; (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]; Status = IScsiAsciiStrToIp (Field->Str, &ConfigNvData->TargetIp); 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; } AsciiStrCpy (ConfigNvData->TargetName, Field->Str); ON_EXIT: gBS->FreePool (TmpStr); return Status; } /** The callback function registerd to the DHCP4 instance which is used to select the qualified DHCP OFFER. @param This[in] The DHCP4 protocol. @param Context[in] The context set when configuring the DHCP4 protocol. @param CurrentState[in] The current state of the DHCP4 protocol. @param Dhcp4Event[in] The event occurs in the current state. @param Packet[in] The DHCP packet that is to be sent or already received. @param NewPackt[out] The packet used to replace the above Packet. @retval EFI_NOT_READY The DHCP OFFER packet doesn't match our requirements. @retval EFI_SUCCESS Either the DHCP OFFER is qualified or we're not intereseted in the Dhcp4Event. **/ STATIC EFI_STATUS 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)) { gBS->FreePool (OptionList); return EFI_NOT_READY; } for (Index = 0; Index < OptionCount; Index++) { if (OptionList[Index]->OpCode != DHCP4_TAG_ROOT_PATH) { continue; } Status = IScsiDhcpExtractRootPath ( (CHAR8 *) &OptionList[Index]->Data[0], OptionList[Index]->Length, (ISCSI_SESSION_CONFIG_NVDATA *) Context ); break; } if ((Index == OptionCount)) { Status = EFI_NOT_READY; } gBS->FreePool (OptionList); return Status; } /** Parse the DHCP ACK to get the address configuration and DNS information. @param Dhcp4[in] The DHCP4 protocol. @param ConfigData[in] 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 mal-formatted. @retval EFI_DEVICE_ERROR Some unexpected error happened. **/ EFI_STATUS IScsiParseDhcpAck ( IN EFI_DHCP4_PROTOCOL *Dhcp4, IN ISCSI_SESSION_CONFIG_DATA *ConfigData ) { EFI_STATUS Status; EFI_DHCP4_MODE_DATA Dhcp4ModeData; UINT32 OptionCount; EFI_DHCP4_PACKET_OPTION **OptionList; UINT32 Index; Status = Dhcp4->GetModeData (Dhcp4, &Dhcp4ModeData); if (EFI_ERROR (Status)) { return Status; } if (Dhcp4ModeData.State != Dhcp4Bound) { return EFI_NO_MAPPING; } CopyMem (&ConfigData->NvData.LocalIp, &Dhcp4ModeData.ClientAddress, sizeof (EFI_IPv4_ADDRESS)); CopyMem (&ConfigData->NvData.SubnetMask, &Dhcp4ModeData.SubnetMask, sizeof (EFI_IPv4_ADDRESS)); CopyMem (&ConfigData->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)) { gBS->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) { 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)); } } gBS->FreePool (OptionList); return Status; } /** Parse the DHCP ACK to get the address configuration and DNS information. @param Image[in] The handle of the driver image. @param Controller[in] The handle of the controller; @param ConfigData[in] 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 mal-formatted. @retval EFI_DEVICE_ERROR Some unexpected error happened. **/ EFI_STATUS IScsiDoDhcp ( IN EFI_HANDLE Image, IN EFI_HANDLE Controller, IN ISCSI_SESSION_CONFIG_DATA *ConfigData ) { EFI_HANDLE Dhcp4Handle; EFI_DHCP4_PROTOCOL *Dhcp4; EFI_STATUS Status; EFI_DHCP4_PACKET_OPTION *ParaList; EFI_DHCP4_CONFIG_DATA Dhcp4ConfigData; Dhcp4Handle = NULL; Dhcp4 = NULL; ParaList = NULL; // // 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; } 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) (ConfigData->NvData.TargetInfoFromDhcp ? 4 : 3); ParaList->Data[0] = DHCP4_TAG_NETMASK; ParaList->Data[1] = DHCP4_TAG_ROUTER; ParaList->Data[2] = DHCP4_TAG_DNS; ParaList->Data[3] = DHCP4_TAG_ROOT_PATH; ZeroMem (&Dhcp4ConfigData, sizeof (EFI_DHCP4_CONFIG_DATA)); Dhcp4ConfigData.OptionCount = 1; Dhcp4ConfigData.OptionList = &ParaList; if (ConfigData->NvData.TargetInfoFromDhcp) { // // Use callback to select an offer which contains target information. // Dhcp4ConfigData.Dhcp4Callback = IScsiDhcpSelectOffer; Dhcp4ConfigData.CallbackContext = &ConfigData->NvData; } 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) { gBS->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; }