/** @file
Functions implementation related with DHCPv4/v6 for DNS driver.
Copyright (c) 2015, Intel Corporation. All rights reserved.<BR>
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 "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;
BOOLEAN MediaPresent;
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.
//
MediaPresent = TRUE;
NetLibDetectMedia (Controller, &MediaPresent);
if (!MediaPresent) {
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_INFORM;
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;
BOOLEAN MediaPresent;
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.
//
MediaPresent = TRUE;
NetLibDetectMedia (Controller, &MediaPresent);
if (!MediaPresent) {
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;
}