/** @file Functions implementation related with DHCPv4/v6 for DNS driver. Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "DnsImpl.h" /** This function initialize the DHCP4 message instance. This function will pad each item of dhcp4 message packet. @param Seed Pointer to the message instance of the DHCP4 packet. @param InterfaceInfo Pointer to the EFI_IP4_CONFIG2_INTERFACE_INFO instance. **/ VOID DnsInitSeedPacket ( OUT EFI_DHCP4_PACKET *Seed, IN EFI_IP4_CONFIG2_INTERFACE_INFO *InterfaceInfo ) { EFI_DHCP4_HEADER *Header; // // Get IfType and HwAddressSize from SNP mode data. // Seed->Size = sizeof (EFI_DHCP4_PACKET); Seed->Length = sizeof (Seed->Dhcp4); Header = &Seed->Dhcp4.Header; ZeroMem (Header, sizeof (EFI_DHCP4_HEADER)); Header->OpCode = DHCP4_OPCODE_REQUEST; Header->HwType = InterfaceInfo->IfType; Header->HwAddrLen = (UINT8) InterfaceInfo->HwAddressSize; CopyMem (Header->ClientHwAddr, &(InterfaceInfo->HwAddress), Header->HwAddrLen); Seed->Dhcp4.Magik = DHCP4_MAGIC; Seed->Dhcp4.Option[0] = DHCP4_TAG_EOP; } /** The common notify function. @param[in] Event The event signaled. @param[in] Context The context. **/ VOID EFIAPI DhcpCommonNotify ( IN EFI_EVENT Event, IN VOID *Context ) { if ((Event == NULL) || (Context == NULL)) { return ; } *((BOOLEAN *) Context) = TRUE; } /** Parse the ACK to get required information @param Dhcp4 The DHCP4 protocol. @param Packet Packet waiting for parse. @param DnsServerInfor The required Dns4 server information. @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_DEVICE_ERROR Other errors as indicated. @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. **/ EFI_STATUS ParseDhcp4Ack ( IN EFI_DHCP4_PROTOCOL *Dhcp4, IN EFI_DHCP4_PACKET *Packet, IN DNS4_SERVER_INFOR *DnsServerInfor ) { EFI_STATUS Status; UINT32 OptionCount; EFI_DHCP4_PACKET_OPTION **OptionList; UINT32 ServerCount; EFI_IPv4_ADDRESS *ServerList; UINT32 Index; UINT32 Count; ServerCount = 0; ServerList = NULL; OptionCount = 0; OptionList = NULL; Status = Dhcp4->Parse (Dhcp4, Packet, &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, Packet, &OptionCount, OptionList); if (EFI_ERROR (Status)) { gBS->FreePool (OptionList); return EFI_DEVICE_ERROR; } Status = EFI_NOT_FOUND; for (Index = 0; Index < OptionCount; Index++) { // // Get DNS server addresses // if (OptionList[Index]->OpCode == DHCP4_TAG_DNS_SERVER) { if (((OptionList[Index]->Length & 0x3) != 0) || (OptionList[Index]->Length == 0)) { Status = EFI_DEVICE_ERROR; break; } ServerCount = OptionList[Index]->Length/4; ServerList = AllocatePool (ServerCount * sizeof (EFI_IPv4_ADDRESS)); if (ServerList == NULL) { return EFI_OUT_OF_RESOURCES; } for (Count=0; Count < ServerCount; Count++) { CopyMem (ServerList + Count, &OptionList[Index]->Data[4 * Count], sizeof (EFI_IPv4_ADDRESS)); } *(DnsServerInfor->ServerCount) = ServerCount; DnsServerInfor->ServerList = ServerList; Status = EFI_SUCCESS; } } gBS->FreePool (OptionList); 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 This Pointer to the EFI_DHCP6_PROTOCOL instance that is used to configure this callback function. @param Context Pointer to the context that is initialized in the EFI_DHCP6_PROTOCOL.InfoRequest(). @param Packet Pointer to Reply packet that has been received. The EFI DHCPv6 Protocol instance is responsible for freeing the buffer. @retval EFI_SUCCESS The DNS information is got from the DHCP ACK. @retval EFI_DEVICE_ERROR Other errors as indicated. @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. **/ EFI_STATUS EFIAPI ParseDhcp6Ack ( IN EFI_DHCP6_PROTOCOL *This, IN VOID *Context, IN EFI_DHCP6_PACKET *Packet ) { EFI_STATUS Status; UINT32 OptionCount; EFI_DHCP6_PACKET_OPTION **OptionList; DNS6_SERVER_INFOR *DnsServerInfor; UINT32 ServerCount; EFI_IPv6_ADDRESS *ServerList; UINT32 Index; UINT32 Count; OptionCount = 0; ServerCount = 0; ServerList = NULL; Status = This->Parse (This, Packet, &OptionCount, NULL); if (Status != EFI_BUFFER_TOO_SMALL) { return EFI_DEVICE_ERROR; } OptionList = AllocateZeroPool (OptionCount * sizeof (EFI_DHCP6_PACKET_OPTION *)); if (OptionList == NULL) { return EFI_OUT_OF_RESOURCES; } Status = This->Parse (This, Packet, &OptionCount, OptionList); if (EFI_ERROR (Status)) { gBS->FreePool (OptionList); return EFI_DEVICE_ERROR; } DnsServerInfor = (DNS6_SERVER_INFOR *) 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_TAG_DNS_SERVER) { if (((OptionList[Index]->OpLen & 0xf) != 0) || (OptionList[Index]->OpLen == 0)) { Status = EFI_DEVICE_ERROR; gBS->FreePool (OptionList); return Status; } ServerCount = OptionList[Index]->OpLen/16; ServerList = AllocatePool (ServerCount * sizeof (EFI_IPv6_ADDRESS)); if (ServerList == NULL) { gBS->FreePool (OptionList); return EFI_OUT_OF_RESOURCES; } for (Count=0; Count < ServerCount; Count++) { CopyMem (ServerList + Count, &OptionList[Index]->Data[16 * Count], sizeof (EFI_IPv6_ADDRESS)); } *(DnsServerInfor->ServerCount) = ServerCount; DnsServerInfor->ServerList = ServerList; } } gBS->FreePool (OptionList); return Status; } /** Parse the DHCP ACK to get Dns4 server information. @param Instance The DNS instance. @param DnsServerCount Retrieved Dns4 server Ip count. @param DnsServerList Retrieved Dns4 server Ip list. @retval EFI_SUCCESS The Dns4 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 GetDns4ServerFromDhcp4 ( IN DNS_INSTANCE *Instance, OUT UINT32 *DnsServerCount, OUT EFI_IPv4_ADDRESS **DnsServerList ) { EFI_STATUS Status; EFI_HANDLE Image; EFI_HANDLE Controller; EFI_STATUS MediaStatus; EFI_HANDLE MnpChildHandle; EFI_MANAGED_NETWORK_PROTOCOL *Mnp; EFI_MANAGED_NETWORK_CONFIG_DATA MnpConfigData; EFI_HANDLE Dhcp4Handle; EFI_DHCP4_PROTOCOL *Dhcp4; EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2; UINTN DataSize; VOID *Data; EFI_IP4_CONFIG2_INTERFACE_INFO *InterfaceInfo; EFI_DHCP4_PACKET SeedPacket; EFI_DHCP4_PACKET_OPTION *ParaList[2]; DNS4_SERVER_INFOR DnsServerInfor; EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN Token; BOOLEAN IsDone; UINTN Index; Image = Instance->Service->ImageHandle; Controller = Instance->Service->ControllerHandle; MnpChildHandle = NULL; Mnp = NULL; Dhcp4Handle = NULL; Dhcp4 = NULL; Ip4Config2 = NULL; DataSize = 0; Data = NULL; InterfaceInfo = NULL; ZeroMem ((UINT8 *) ParaList, sizeof (ParaList)); ZeroMem (&MnpConfigData, sizeof (EFI_MANAGED_NETWORK_CONFIG_DATA)); ZeroMem (&DnsServerInfor, sizeof (DNS4_SERVER_INFOR)); ZeroMem (&Token, sizeof (EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN)); DnsServerInfor.ServerCount = DnsServerCount; IsDone = FALSE; // // Check media. // MediaStatus = EFI_SUCCESS; NetLibDetectMediaWaitTimeout (Controller, DNS_CHECK_MEDIA_GET_DHCP_WAITING_TIME, &MediaStatus); if (MediaStatus != EFI_SUCCESS) { return EFI_NO_MEDIA; } // // Create a Mnp child instance, get the protocol and config for it. // Status = NetLibCreateServiceChild ( Controller, Image, &gEfiManagedNetworkServiceBindingProtocolGuid, &MnpChildHandle ); if (EFI_ERROR (Status)) { return Status; } Status = gBS->OpenProtocol ( MnpChildHandle, &gEfiManagedNetworkProtocolGuid, (VOID **) &Mnp, Image, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (EFI_ERROR (Status)) { goto ON_EXIT; } MnpConfigData.ReceivedQueueTimeoutValue = 0; MnpConfigData.TransmitQueueTimeoutValue = 0; MnpConfigData.ProtocolTypeFilter = IP4_ETHER_PROTO; MnpConfigData.EnableUnicastReceive = TRUE; MnpConfigData.EnableMulticastReceive = TRUE; MnpConfigData.EnableBroadcastReceive = TRUE; MnpConfigData.EnablePromiscuousReceive = FALSE; MnpConfigData.FlushQueuesOnReset = TRUE; MnpConfigData.EnableReceiveTimestamps = FALSE; MnpConfigData.DisableBackgroundPolling = FALSE; Status = Mnp->Configure(Mnp, &MnpConfigData); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // Create a DHCP4 child instance and get the protocol. // Status = NetLibCreateServiceChild ( Controller, Image, &gEfiDhcp4ServiceBindingProtocolGuid, &Dhcp4Handle ); if (EFI_ERROR (Status)) { goto ON_EXIT; } Status = gBS->OpenProtocol ( Dhcp4Handle, &gEfiDhcp4ProtocolGuid, (VOID **) &Dhcp4, Image, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // Get Ip4Config2 instance info. // Status = gBS->HandleProtocol (Controller, &gEfiIp4Config2ProtocolGuid, (VOID **) &Ip4Config2); if (EFI_ERROR (Status)) { goto ON_EXIT; } Status = Ip4Config2->GetData (Ip4Config2, Ip4Config2DataTypeInterfaceInfo, &DataSize, Data); if (EFI_ERROR (Status) && Status != EFI_BUFFER_TOO_SMALL) { goto ON_EXIT; } Data = AllocateZeroPool (DataSize); if (Data == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } Status = Ip4Config2->GetData (Ip4Config2, Ip4Config2DataTypeInterfaceInfo, &DataSize, Data); if (EFI_ERROR (Status)) { goto ON_EXIT; } InterfaceInfo = (EFI_IP4_CONFIG2_INTERFACE_INFO *)Data; // // Build required Token. // Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, DhcpCommonNotify, &IsDone, &Token.CompletionEvent ); if (EFI_ERROR (Status)) { goto ON_EXIT; } SetMem (&Token.RemoteAddress, sizeof (EFI_IPv4_ADDRESS), 0xff); Token.RemotePort = 67; Token.ListenPointCount = 1; Token.ListenPoints = AllocateZeroPool (Token.ListenPointCount * sizeof (EFI_DHCP4_LISTEN_POINT)); if (Token.ListenPoints == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } if (Instance->Dns4CfgData.UseDefaultSetting) { CopyMem (&(Token.ListenPoints[0].ListenAddress), &(InterfaceInfo->StationAddress), sizeof (EFI_IPv4_ADDRESS)); CopyMem (&(Token.ListenPoints[0].SubnetMask), &(InterfaceInfo->SubnetMask), sizeof (EFI_IPv4_ADDRESS)); } else { CopyMem (&(Token.ListenPoints[0].ListenAddress), &(Instance->Dns4CfgData.StationIp), sizeof (EFI_IPv4_ADDRESS)); CopyMem (&(Token.ListenPoints[0].SubnetMask), &(Instance->Dns4CfgData.SubnetMask), sizeof (EFI_IPv4_ADDRESS)); } Token.ListenPoints[0].ListenPort = 68; Token.TimeoutValue = DNS_TIME_TO_GETMAP; DnsInitSeedPacket (&SeedPacket, InterfaceInfo); ParaList[0] = AllocateZeroPool (sizeof (EFI_DHCP4_PACKET_OPTION)); if (ParaList[0] == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } ParaList[0]->OpCode = DHCP4_TAG_TYPE; ParaList[0]->Length = 1; ParaList[0]->Data[0] = DHCP4_MSG_REQUEST; ParaList[1] = AllocateZeroPool (sizeof (EFI_DHCP4_PACKET_OPTION)); if (ParaList[1] == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } ParaList[1]->OpCode = DHCP4_TAG_PARA_LIST; ParaList[1]->Length = 1; ParaList[1]->Data[0] = DHCP4_TAG_DNS_SERVER; Status = Dhcp4->Build (Dhcp4, &SeedPacket, 0, NULL, 2, ParaList, &Token.Packet); Token.Packet->Dhcp4.Header.Xid = HTONL(NET_RANDOM (NetRandomInitSeed ())); Token.Packet->Dhcp4.Header.Reserved = HTONS ((UINT16)0x8000); if (Instance->Dns4CfgData.UseDefaultSetting) { CopyMem (&(Token.Packet->Dhcp4.Header.ClientAddr), &(InterfaceInfo->StationAddress), sizeof (EFI_IPv4_ADDRESS)); } else { CopyMem (&(Token.Packet->Dhcp4.Header.ClientAddr), &(Instance->Dns4CfgData.StationIp), sizeof (EFI_IPv4_ADDRESS)); } CopyMem (Token.Packet->Dhcp4.Header.ClientHwAddr, &(InterfaceInfo->HwAddress), InterfaceInfo->HwAddressSize); Token.Packet->Dhcp4.Header.HwAddrLen = (UINT8)(InterfaceInfo->HwAddressSize); // // TransmitReceive Token // Status = Dhcp4->TransmitReceive (Dhcp4, &Token); if (EFI_ERROR (Status)) { goto ON_EXIT; } // // Poll the packet // do { Status = Mnp->Poll (Mnp); } while (!IsDone); // // Parse the ACK to get required information if received done. // if (IsDone && !EFI_ERROR (Token.Status)) { for (Index = 0; Index < Token.ResponseCount; Index++) { Status = ParseDhcp4Ack (Dhcp4, &Token.ResponseList[Index], &DnsServerInfor); if (!EFI_ERROR (Status)) { break; } } *DnsServerList = DnsServerInfor.ServerList; } else { Status = Token.Status; } ON_EXIT: if (Data != NULL) { FreePool (Data); } for (Index = 0; Index < 2; Index++) { if (ParaList[Index] != NULL) { FreePool (ParaList[Index]); } } if (Token.ListenPoints) { FreePool (Token.ListenPoints); } if (Token.Packet) { FreePool (Token.Packet); } if (Token.ResponseList != NULL) { FreePool (Token.ResponseList); } if (Token.CompletionEvent != NULL) { gBS->CloseEvent (Token.CompletionEvent); } if (Dhcp4 != NULL) { Dhcp4->Stop (Dhcp4); Dhcp4->Configure (Dhcp4, NULL); gBS->CloseProtocol ( Dhcp4Handle, &gEfiDhcp4ProtocolGuid, Image, Controller ); } if (Dhcp4Handle != NULL) { NetLibDestroyServiceChild ( Controller, Image, &gEfiDhcp4ServiceBindingProtocolGuid, Dhcp4Handle ); } if (Mnp != NULL) { Mnp->Configure (Mnp, NULL); gBS->CloseProtocol ( MnpChildHandle, &gEfiManagedNetworkProtocolGuid, Image, Controller ); } NetLibDestroyServiceChild ( Controller, Image, &gEfiManagedNetworkServiceBindingProtocolGuid, MnpChildHandle ); return Status; } /** Parse the DHCP ACK to get Dns6 server information. @param Image The handle of the driver image. @param Controller The handle of the controller. @param DnsServerCount Retrieved Dns6 server Ip count. @param DnsServerList Retrieved Dns6 server Ip list. @retval EFI_SUCCESS The Dns6 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 GetDns6ServerFromDhcp6 ( IN EFI_HANDLE Image, IN EFI_HANDLE Controller, OUT UINT32 *DnsServerCount, OUT EFI_IPv6_ADDRESS **DnsServerList ) { 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; EFI_STATUS MediaStatus; DNS6_SERVER_INFOR DnsServerInfor; Dhcp6Handle = NULL; Dhcp6 = NULL; Oro = NULL; Timer = NULL; ZeroMem (&DnsServerInfor, sizeof (DNS6_SERVER_INFOR)); DnsServerInfor.ServerCount = DnsServerCount; // // Check media status before doing DHCP. // MediaStatus = EFI_SUCCESS; NetLibDetectMediaWaitTimeout (Controller, DNS_CHECK_MEDIA_GET_DHCP_WAITING_TIME, &MediaStatus); if (MediaStatus != EFI_SUCCESS) { return EFI_NO_MEDIA; } // // 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) + 1); if (Oro == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ON_EXIT; } // // Ask the server to reply with DNS options. // All members in EFI_DHCP6_PACKET_OPTION are in network order. // Oro->OpCode = HTONS (DHCP6_TAG_DNS_REQUEST); Oro->OpLen = HTONS (2); Oro->Data[1] = DHCP6_TAG_DNS_SERVER; InfoReqReXmit.Irt = 4; InfoReqReXmit.Mrc = 1; InfoReqReXmit.Mrt = 10; InfoReqReXmit.Mrd = 30; Status = Dhcp6->InfoRequest ( Dhcp6, TRUE, Oro, 0, NULL, &InfoReqReXmit, NULL, ParseDhcp6Ack, &DnsServerInfor ); 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, DNS_TIME_TO_GETMAP * TICKS_PER_SECOND ); 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, ParseDhcp6Ack, &DnsServerInfor ); } } while (TimerStatus == EFI_NOT_READY); } *DnsServerList = DnsServerInfor.ServerList; 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; }