mirror of https://github.com/acidanthera/audk.git
1645 lines
43 KiB
C
1645 lines
43 KiB
C
/** @file
|
|
EFI DHCP protocol implementation.
|
|
|
|
Copyright (c) 2006 - 2010, 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 "Dhcp4Impl.h"
|
|
|
|
UINT32 mDhcp4DefaultTimeout[4] = { 4, 8, 16, 32 };
|
|
|
|
|
|
/**
|
|
Send an initial DISCOVER or REQUEST message according to the
|
|
DHCP service's current state.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
|
|
@retval EFI_SUCCESS The request has been sent
|
|
@retval other Some error occurs when sending the request.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpInitRequest (
|
|
IN DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
ASSERT ((DhcpSb->DhcpState == Dhcp4Init) || (DhcpSb->DhcpState == Dhcp4InitReboot));
|
|
|
|
if (DhcpSb->DhcpState == Dhcp4Init) {
|
|
DhcpSetState (DhcpSb, Dhcp4Selecting, FALSE);
|
|
Status = DhcpSendMessage (DhcpSb, NULL, NULL, DHCP_MSG_DISCOVER, NULL);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DhcpSb->DhcpState = Dhcp4Init;
|
|
return Status;
|
|
}
|
|
} else {
|
|
DhcpSetState (DhcpSb, Dhcp4Rebooting, FALSE);
|
|
Status = DhcpSendMessage (DhcpSb, NULL, NULL, DHCP_MSG_REQUEST, NULL);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DhcpSb->DhcpState = Dhcp4InitReboot;
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Call user provided callback function, and return the value the
|
|
function returns. If the user doesn't provide a callback, a
|
|
proper return value is selected to let the caller continue the
|
|
normal process.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Event The event as defined in the spec
|
|
@param[in] Packet The current packet trigger the event
|
|
@param[out] NewPacket The user's return new packet
|
|
|
|
@retval EFI_NOT_READY Direct the caller to continue collecting the offer.
|
|
@retval EFI_SUCCESS The user function returns success.
|
|
@retval EFI_ABORTED The user function ask it to abort.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpCallUser (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_EVENT Event,
|
|
IN EFI_DHCP4_PACKET *Packet, OPTIONAL
|
|
OUT EFI_DHCP4_PACKET **NewPacket OPTIONAL
|
|
)
|
|
{
|
|
EFI_DHCP4_CONFIG_DATA *Config;
|
|
EFI_STATUS Status;
|
|
|
|
if (NewPacket != NULL) {
|
|
*NewPacket = NULL;
|
|
}
|
|
|
|
//
|
|
// If user doesn't provide the call back function, return the value
|
|
// that directs the client to continue the normal process.
|
|
// In Dhcp4Selecting EFI_SUCCESS tells the client to stop collecting
|
|
// the offers and select a offer, EFI_NOT_READY tells the client to
|
|
// collect more offers.
|
|
//
|
|
Config = &DhcpSb->ActiveConfig;
|
|
|
|
if (Config->Dhcp4Callback == NULL) {
|
|
if (Event == Dhcp4RcvdOffer) {
|
|
return EFI_NOT_READY;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
Status = Config->Dhcp4Callback (
|
|
&DhcpSb->ActiveChild->Dhcp4Protocol,
|
|
Config->CallbackContext,
|
|
(EFI_DHCP4_STATE) DhcpSb->DhcpState,
|
|
Event,
|
|
Packet,
|
|
NewPacket
|
|
);
|
|
|
|
//
|
|
// User's callback should only return EFI_SUCCESS, EFI_NOT_READY,
|
|
// and EFI_ABORTED. If it returns values other than those, assume
|
|
// it to be EFI_ABORTED.
|
|
//
|
|
if ((Status == EFI_SUCCESS) || (Status == EFI_NOT_READY)) {
|
|
return Status;
|
|
}
|
|
|
|
return EFI_ABORTED;
|
|
}
|
|
|
|
|
|
/**
|
|
Notify the user about the operation result.
|
|
|
|
@param DhcpSb DHCP service instance
|
|
@param Which Which notify function to signal
|
|
|
|
**/
|
|
VOID
|
|
DhcpNotifyUser (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN INTN Which
|
|
)
|
|
{
|
|
DHCP_PROTOCOL *Child;
|
|
|
|
if ((Child = DhcpSb->ActiveChild) == NULL) {
|
|
return ;
|
|
}
|
|
|
|
if ((Child->CompletionEvent != NULL) &&
|
|
((Which == DHCP_NOTIFY_COMPLETION) || (Which == DHCP_NOTIFY_ALL))
|
|
) {
|
|
|
|
gBS->SignalEvent (Child->CompletionEvent);
|
|
Child->CompletionEvent = NULL;
|
|
}
|
|
|
|
if ((Child->RenewRebindEvent != NULL) &&
|
|
((Which == DHCP_NOTIFY_RENEWREBIND) || (Which == DHCP_NOTIFY_ALL))
|
|
) {
|
|
|
|
gBS->SignalEvent (Child->RenewRebindEvent);
|
|
Child->RenewRebindEvent = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Set the DHCP state. If CallUser is true, it will try to notify
|
|
the user before change the state by DhcpNotifyUser. It returns
|
|
EFI_ABORTED if the user return EFI_ABORTED, otherwise, it returns
|
|
EFI_SUCCESS. If CallUser is FALSE, it isn't necessary to test
|
|
the return value of this function.
|
|
|
|
@param DhcpSb The DHCP service instance
|
|
@param State The new DHCP state to change to
|
|
@param CallUser Whether we need to call user
|
|
|
|
@retval EFI_SUCCESS The state is changed
|
|
@retval EFI_ABORTED The user asks to abort the DHCP process.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpSetState (
|
|
IN OUT DHCP_SERVICE *DhcpSb,
|
|
IN INTN State,
|
|
IN BOOLEAN CallUser
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
if (CallUser) {
|
|
Status = EFI_SUCCESS;
|
|
|
|
if (State == Dhcp4Renewing) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4EnterRenewing, NULL, NULL);
|
|
|
|
} else if (State == Dhcp4Rebinding) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4EnterRebinding, NULL, NULL);
|
|
|
|
} else if (State == Dhcp4Bound) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4BoundCompleted, NULL, NULL);
|
|
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the retransmission timer during the state transition.
|
|
// This will clear the retry count. This is also why the rule
|
|
// first transit the state, then send packets.
|
|
//
|
|
if (State == Dhcp4Selecting) {
|
|
DhcpSb->MaxRetries = DhcpSb->ActiveConfig.DiscoverTryCount;
|
|
} else {
|
|
DhcpSb->MaxRetries = DhcpSb->ActiveConfig.RequestTryCount;
|
|
}
|
|
|
|
if (DhcpSb->MaxRetries == 0) {
|
|
DhcpSb->MaxRetries = 4;
|
|
}
|
|
|
|
DhcpSb->CurRetry = 0;
|
|
DhcpSb->PacketToLive = 0;
|
|
DhcpSb->LastTimeout = 0;
|
|
DhcpSb->DhcpState = State;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Set the retransmit timer for the packet. It will select from either
|
|
the discover timeouts/request timeouts or the default timeout values.
|
|
|
|
@param DhcpSb The DHCP service instance.
|
|
|
|
**/
|
|
VOID
|
|
DhcpSetTransmitTimer (
|
|
IN OUT DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
UINT32 *Times;
|
|
|
|
ASSERT (DhcpSb->MaxRetries > DhcpSb->CurRetry);
|
|
|
|
if (DhcpSb->DhcpState == Dhcp4Selecting) {
|
|
Times = DhcpSb->ActiveConfig.DiscoverTimeout;
|
|
} else {
|
|
Times = DhcpSb->ActiveConfig.RequestTimeout;
|
|
}
|
|
|
|
if (Times == NULL) {
|
|
Times = mDhcp4DefaultTimeout;
|
|
}
|
|
|
|
DhcpSb->PacketToLive = Times[DhcpSb->CurRetry];
|
|
DhcpSb->LastTimeout = DhcpSb->PacketToLive;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
Compute the lease. If the server grants a permanent lease, just
|
|
process it as a normal timeout value since the lease will last
|
|
more than 100 years.
|
|
|
|
@param DhcpSb The DHCP service instance
|
|
@param Para The DHCP parameter extracted from the server's
|
|
response.
|
|
**/
|
|
VOID
|
|
DhcpComputeLease (
|
|
IN OUT DHCP_SERVICE *DhcpSb,
|
|
IN DHCP_PARAMETER *Para
|
|
)
|
|
{
|
|
ASSERT (Para != NULL);
|
|
|
|
DhcpSb->Lease = Para->Lease;
|
|
DhcpSb->T2 = Para->T2;
|
|
DhcpSb->T1 = Para->T1;
|
|
|
|
if (DhcpSb->Lease == 0) {
|
|
DhcpSb->Lease = DHCP_DEFAULT_LEASE;
|
|
}
|
|
|
|
if ((DhcpSb->T2 == 0) || (DhcpSb->T2 >= Para->Lease)) {
|
|
DhcpSb->T2 = Para->Lease - (Para->Lease >> 3);
|
|
}
|
|
|
|
if ((DhcpSb->T1 == 0) || (DhcpSb->T1 >= Para->T2)) {
|
|
DhcpSb->T1 = DhcpSb->Lease >> 1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Configure a UDP IO port to use the acquired lease address.
|
|
DHCP driver needs this port to unicast packet to the server
|
|
such as DHCP release.
|
|
|
|
@param[in] UdpIo The UDP IO to configure
|
|
@param[in] Context Dhcp service instance.
|
|
|
|
@retval EFI_SUCCESS The UDP IO port is successfully configured.
|
|
@retval Others It failed to configure the port.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
DhcpConfigLeaseIoPort (
|
|
IN UDP_IO *UdpIo,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
EFI_UDP4_CONFIG_DATA UdpConfigData;
|
|
EFI_IPv4_ADDRESS Subnet;
|
|
EFI_IPv4_ADDRESS Gateway;
|
|
DHCP_SERVICE *DhcpSb;
|
|
EFI_STATUS Status;
|
|
IP4_ADDR Ip;
|
|
|
|
DhcpSb = (DHCP_SERVICE *) Context;
|
|
|
|
UdpConfigData.AcceptBroadcast = FALSE;
|
|
UdpConfigData.AcceptPromiscuous = FALSE;
|
|
UdpConfigData.AcceptAnyPort = FALSE;
|
|
UdpConfigData.AllowDuplicatePort = TRUE;
|
|
UdpConfigData.TypeOfService = 0;
|
|
UdpConfigData.TimeToLive = 64;
|
|
UdpConfigData.DoNotFragment = FALSE;
|
|
UdpConfigData.ReceiveTimeout = 1;
|
|
UdpConfigData.TransmitTimeout = 0;
|
|
|
|
UdpConfigData.UseDefaultAddress = FALSE;
|
|
UdpConfigData.StationPort = DHCP_CLIENT_PORT;
|
|
UdpConfigData.RemotePort = DHCP_SERVER_PORT;
|
|
|
|
Ip = HTONL (DhcpSb->ClientAddr);
|
|
CopyMem (&UdpConfigData.StationAddress, &Ip, sizeof (EFI_IPv4_ADDRESS));
|
|
|
|
Ip = HTONL (DhcpSb->Netmask);
|
|
CopyMem (&UdpConfigData.SubnetMask, &Ip, sizeof (EFI_IPv4_ADDRESS));
|
|
|
|
ZeroMem (&UdpConfigData.RemoteAddress, sizeof (EFI_IPv4_ADDRESS));
|
|
|
|
Status = UdpIo->Protocol.Udp4->Configure (UdpIo->Protocol.Udp4, &UdpConfigData);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Add a default route if received from the server.
|
|
//
|
|
if ((DhcpSb->Para != NULL) && (DhcpSb->Para->Router != 0)) {
|
|
ZeroMem (&Subnet, sizeof (EFI_IPv4_ADDRESS));
|
|
|
|
Ip = HTONL (DhcpSb->Para->Router);
|
|
CopyMem (&Gateway, &Ip, sizeof (EFI_IPv4_ADDRESS));
|
|
|
|
UdpIo->Protocol.Udp4->Routes (UdpIo->Protocol.Udp4, FALSE, &Subnet, &Subnet, &Gateway);
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Update the lease states when a new lease is acquired. It will not only
|
|
save the acquired the address and lease time, it will also create a UDP
|
|
child to provide address resolution for the address.
|
|
|
|
@param DhcpSb The DHCP service instance
|
|
|
|
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources.
|
|
@retval EFI_SUCCESS The lease is recorded.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpLeaseAcquired (
|
|
IN OUT DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
INTN Class;
|
|
|
|
DhcpSb->ClientAddr = EFI_NTOHL (DhcpSb->Selected->Dhcp4.Header.YourAddr);
|
|
|
|
if (DhcpSb->Para != NULL) {
|
|
DhcpSb->Netmask = DhcpSb->Para->NetMask;
|
|
DhcpSb->ServerAddr = DhcpSb->Para->ServerId;
|
|
}
|
|
|
|
if (DhcpSb->Netmask == 0) {
|
|
Class = NetGetIpClass (DhcpSb->ClientAddr);
|
|
DhcpSb->Netmask = gIp4AllMasks[Class << 3];
|
|
}
|
|
|
|
if (DhcpSb->LeaseIoPort != NULL) {
|
|
UdpIoFreeIo (DhcpSb->LeaseIoPort);
|
|
}
|
|
|
|
//
|
|
// Create a UDP/IP child to provide ARP service for the Leased IP,
|
|
// and transmit unicast packet with it as source address. Don't
|
|
// start receive on this port, the queued packet will be timeout.
|
|
//
|
|
DhcpSb->LeaseIoPort = UdpIoCreateIo (
|
|
DhcpSb->Controller,
|
|
DhcpSb->Image,
|
|
DhcpConfigLeaseIoPort,
|
|
UDP_IO_UDP4_VERSION,
|
|
DhcpSb
|
|
);
|
|
|
|
if (DhcpSb->LeaseIoPort == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
if (!DHCP_IS_BOOTP (DhcpSb->Para)) {
|
|
DhcpComputeLease (DhcpSb, DhcpSb->Para);
|
|
}
|
|
|
|
return DhcpSetState (DhcpSb, Dhcp4Bound, TRUE);
|
|
}
|
|
|
|
|
|
/**
|
|
Clean up the DHCP related states, IoStatus isn't reset.
|
|
|
|
@param DhcpSb The DHCP instance service.
|
|
|
|
**/
|
|
VOID
|
|
DhcpCleanLease (
|
|
IN DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
DhcpSb->DhcpState = Dhcp4Init;
|
|
DhcpSb->Xid = DhcpSb->Xid + 1;
|
|
DhcpSb->ClientAddr = 0;
|
|
DhcpSb->Netmask = 0;
|
|
DhcpSb->ServerAddr = 0;
|
|
|
|
if (DhcpSb->LastOffer != NULL) {
|
|
FreePool (DhcpSb->LastOffer);
|
|
DhcpSb->LastOffer = NULL;
|
|
}
|
|
|
|
if (DhcpSb->Selected != NULL) {
|
|
FreePool (DhcpSb->Selected);
|
|
DhcpSb->Selected = NULL;
|
|
}
|
|
|
|
if (DhcpSb->Para != NULL) {
|
|
FreePool (DhcpSb->Para);
|
|
DhcpSb->Para = NULL;
|
|
}
|
|
|
|
DhcpSb->Lease = 0;
|
|
DhcpSb->T1 = 0;
|
|
DhcpSb->T2 = 0;
|
|
DhcpSb->ExtraRefresh = FALSE;
|
|
|
|
if (DhcpSb->LeaseIoPort != NULL) {
|
|
UdpIoFreeIo (DhcpSb->LeaseIoPort);
|
|
DhcpSb->LeaseIoPort = NULL;
|
|
}
|
|
|
|
if (DhcpSb->LastPacket != NULL) {
|
|
FreePool (DhcpSb->LastPacket);
|
|
DhcpSb->LastPacket = NULL;
|
|
}
|
|
|
|
DhcpSb->PacketToLive = 0;
|
|
DhcpSb->LastTimeout = 0;
|
|
DhcpSb->CurRetry = 0;
|
|
DhcpSb->MaxRetries = 0;
|
|
DhcpSb->LeaseLife = 0;
|
|
|
|
//
|
|
// Clean active config data.
|
|
//
|
|
DhcpCleanConfigure (&DhcpSb->ActiveConfig);
|
|
}
|
|
|
|
|
|
/**
|
|
Select a offer among all the offers collected. If the offer selected is
|
|
of BOOTP, the lease is recorded and user notified. If the offer is of
|
|
DHCP, it will request the offer from the server.
|
|
|
|
@param[in] DhcpSb The DHCP service instance.
|
|
|
|
@retval EFI_SUCCESS One of the offer is selected.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpChooseOffer (
|
|
IN DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
EFI_DHCP4_PACKET *Selected;
|
|
EFI_DHCP4_PACKET *NewPacket;
|
|
EFI_DHCP4_PACKET *TempPacket;
|
|
EFI_STATUS Status;
|
|
|
|
ASSERT (DhcpSb->LastOffer != NULL);
|
|
|
|
//
|
|
// User will cache previous offers if he wants to select
|
|
// from multiple offers. If user provides an invalid packet,
|
|
// use the last offer, otherwise use the provided packet.
|
|
//
|
|
NewPacket = NULL;
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4SelectOffer, DhcpSb->LastOffer, &NewPacket);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Selected = DhcpSb->LastOffer;
|
|
|
|
if ((NewPacket != NULL) && !EFI_ERROR (DhcpValidateOptions (NewPacket, NULL))) {
|
|
TempPacket = (EFI_DHCP4_PACKET *) AllocatePool (NewPacket->Size);
|
|
if (TempPacket != NULL) {
|
|
CopyMem (TempPacket, NewPacket, NewPacket->Size);
|
|
FreePool (Selected);
|
|
Selected = TempPacket;
|
|
}
|
|
}
|
|
|
|
DhcpSb->Selected = Selected;
|
|
DhcpSb->LastOffer = NULL;
|
|
DhcpSb->Para = NULL;
|
|
DhcpValidateOptions (Selected, &DhcpSb->Para);
|
|
|
|
//
|
|
// A bootp offer has been selected, save the lease status,
|
|
// enter bound state then notify the user.
|
|
//
|
|
if (DHCP_IS_BOOTP (DhcpSb->Para)) {
|
|
Status = DhcpLeaseAcquired (DhcpSb);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
DhcpSb->IoStatus = EFI_SUCCESS;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_ALL);
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Send a DHCP requests
|
|
//
|
|
Status = DhcpSetState (DhcpSb, Dhcp4Requesting, TRUE);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
return DhcpSendMessage (DhcpSb, Selected, DhcpSb->Para, DHCP_MSG_REQUEST, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
Terminate the current address acquire. All the allocated resources
|
|
are released. Be careful when calling this function. A rule related
|
|
to this is: only call DhcpEndSession at the highest level, such as
|
|
DhcpInput, DhcpOnTimerTick...At the other level, just return error.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Status The result of the DHCP process.
|
|
|
|
**/
|
|
VOID
|
|
DhcpEndSession (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_STATUS Status
|
|
)
|
|
{
|
|
if (DHCP_CONNECTED (DhcpSb->DhcpState)) {
|
|
DhcpCallUser (DhcpSb, Dhcp4AddressLost, NULL, NULL);
|
|
} else {
|
|
DhcpCallUser (DhcpSb, Dhcp4Fail, NULL, NULL);
|
|
}
|
|
|
|
DhcpCleanLease (DhcpSb);
|
|
|
|
DhcpSb->IoStatus = Status;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_ALL);
|
|
}
|
|
|
|
|
|
/**
|
|
Handle packets in DHCP select state.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Packet The DHCP packet received
|
|
@param[in] Para The DHCP parameter extracted from the packet. That
|
|
is, all the option value that we care.
|
|
|
|
@retval EFI_SUCCESS The packet is successfully processed.
|
|
@retval Others Some error occured.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpHandleSelect (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_PACKET *Packet,
|
|
IN DHCP_PARAMETER *Para
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
//
|
|
// First validate the message:
|
|
// 1. the offer is a unicast
|
|
// 2. if it is a DHCP message, it must contains a server ID.
|
|
// Don't return a error for these two case otherwise the session is ended.
|
|
//
|
|
if (!DHCP_IS_BOOTP (Para) &&
|
|
((Para->DhcpType != DHCP_MSG_OFFER) || (Para->ServerId == 0))
|
|
) {
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Call the user's callback. The action according to the return is as:
|
|
// 1. EFI_SUCESS: stop waiting for more offers, select the offer now
|
|
// 2. EFI_NOT_READY: wait for more offers
|
|
// 3. EFI_ABORTED: abort the address acquiring.
|
|
//
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4RcvdOffer, Packet, NULL);
|
|
|
|
if (Status == EFI_SUCCESS) {
|
|
if (DhcpSb->LastOffer != NULL) {
|
|
FreePool (DhcpSb->LastOffer);
|
|
}
|
|
|
|
DhcpSb->LastOffer = Packet;
|
|
|
|
return DhcpChooseOffer (DhcpSb);
|
|
|
|
} else if (Status == EFI_NOT_READY) {
|
|
if (DhcpSb->LastOffer != NULL) {
|
|
FreePool (DhcpSb->LastOffer);
|
|
}
|
|
|
|
DhcpSb->LastOffer = Packet;
|
|
|
|
} else if (Status == EFI_ABORTED) {
|
|
//
|
|
// DhcpInput will end the session upon error return. Remember
|
|
// only to call DhcpEndSession at the top level call.
|
|
//
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
ON_EXIT:
|
|
FreePool (Packet);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Handle packets in DHCP request state.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Packet The DHCP packet received
|
|
@param[in] Para The DHCP parameter extracted from the packet. That
|
|
is, all the option value that we care.
|
|
|
|
@retval EFI_SUCCESS The packet is successfully processed.
|
|
@retval Others Some error occured.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpHandleRequest (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_PACKET *Packet,
|
|
IN DHCP_PARAMETER *Para
|
|
)
|
|
{
|
|
EFI_DHCP4_HEADER *Head;
|
|
EFI_DHCP4_HEADER *Selected;
|
|
EFI_STATUS Status;
|
|
UINT8 *Message;
|
|
|
|
ASSERT (!DHCP_IS_BOOTP (DhcpSb->Para));
|
|
|
|
Head = &Packet->Dhcp4.Header;
|
|
Selected = &DhcpSb->Selected->Dhcp4.Header;
|
|
|
|
//
|
|
// Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK.
|
|
//
|
|
if (DHCP_IS_BOOTP (Para) ||
|
|
(Para->ServerId != DhcpSb->Para->ServerId) ||
|
|
((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK))
|
|
) {
|
|
|
|
Status = EFI_SUCCESS;
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Received a NAK, end the session no matter what the user returns
|
|
//
|
|
Status = EFI_DEVICE_ERROR;
|
|
|
|
if (Para->DhcpType == DHCP_MSG_NAK) {
|
|
DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL);
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Check whether the ACK matches the selected offer
|
|
//
|
|
Message = NULL;
|
|
|
|
if (!EFI_IP4_EQUAL (&Head->YourAddr, &Selected->YourAddr)) {
|
|
Message = (UINT8 *) "Lease confirmed isn't the same as that in the offer";
|
|
goto REJECT;
|
|
}
|
|
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Message = (UINT8 *) "Lease is denied upon received ACK";
|
|
goto REJECT;
|
|
}
|
|
|
|
//
|
|
// Record the lease, transit to BOUND state, then notify the user
|
|
//
|
|
Status = DhcpLeaseAcquired (DhcpSb);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Message = (UINT8 *) "Lease is denied upon entering bound";
|
|
goto REJECT;
|
|
}
|
|
|
|
DhcpSb->IoStatus = EFI_SUCCESS;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION);
|
|
|
|
FreePool (Packet);
|
|
return EFI_SUCCESS;
|
|
|
|
REJECT:
|
|
DhcpSendMessage (DhcpSb, DhcpSb->Selected, DhcpSb->Para, DHCP_MSG_DECLINE, Message);
|
|
|
|
ON_EXIT:
|
|
FreePool (Packet);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Handle packets in DHCP renew/rebound state.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Packet The DHCP packet received
|
|
@param[in] Para The DHCP parameter extracted from the packet. That
|
|
is, all the option value that we care.
|
|
|
|
@retval EFI_SUCCESS The packet is successfully processed.
|
|
@retval Others Some error occured.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpHandleRenewRebind (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_PACKET *Packet,
|
|
IN DHCP_PARAMETER *Para
|
|
)
|
|
{
|
|
EFI_DHCP4_HEADER *Head;
|
|
EFI_DHCP4_HEADER *Selected;
|
|
EFI_STATUS Status;
|
|
|
|
ASSERT (!DHCP_IS_BOOTP (DhcpSb->Para));
|
|
|
|
Head = &Packet->Dhcp4.Header;
|
|
Selected = &DhcpSb->Selected->Dhcp4.Header;
|
|
|
|
//
|
|
// Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK
|
|
//
|
|
if (DHCP_IS_BOOTP (Para) ||
|
|
(Para->ServerId != DhcpSb->Para->ServerId) ||
|
|
((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK))
|
|
) {
|
|
|
|
Status = EFI_SUCCESS;
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Received a NAK, ignore the user's return then terminate the process
|
|
//
|
|
Status = EFI_DEVICE_ERROR;
|
|
|
|
if (Para->DhcpType == DHCP_MSG_NAK) {
|
|
DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL);
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// The lease is different from the selected. Don't send a DECLINE
|
|
// since it isn't existed in the client's FSM.
|
|
//
|
|
if (!EFI_IP4_EQUAL (&Head->YourAddr, &Selected->YourAddr)) {
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Record the lease, start timer for T1 and T2,
|
|
//
|
|
DhcpComputeLease (DhcpSb, Para);
|
|
DhcpSb->LeaseLife = 0;
|
|
DhcpSetState (DhcpSb, Dhcp4Bound, TRUE);
|
|
|
|
if (DhcpSb->ExtraRefresh != 0) {
|
|
DhcpSb->ExtraRefresh = FALSE;
|
|
|
|
DhcpSb->IoStatus = EFI_SUCCESS;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_RENEWREBIND);
|
|
}
|
|
|
|
ON_EXIT:
|
|
FreePool (Packet);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Handle packets in DHCP reboot state.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Packet The DHCP packet received
|
|
@param[in] Para The DHCP parameter extracted from the packet. That
|
|
is, all the option value that we care.
|
|
|
|
@retval EFI_SUCCESS The packet is successfully processed.
|
|
@retval Others Some error occured.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpHandleReboot (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_PACKET *Packet,
|
|
IN DHCP_PARAMETER *Para
|
|
)
|
|
{
|
|
EFI_DHCP4_HEADER *Head;
|
|
EFI_STATUS Status;
|
|
|
|
Head = &Packet->Dhcp4.Header;
|
|
|
|
//
|
|
// Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK
|
|
//
|
|
if (DHCP_IS_BOOTP (Para) ||
|
|
((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK))
|
|
) {
|
|
|
|
Status = EFI_SUCCESS;
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// If a NAK is received, transit to INIT and try again.
|
|
//
|
|
if (Para->DhcpType == DHCP_MSG_NAK) {
|
|
DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL);
|
|
|
|
DhcpSb->ClientAddr = 0;
|
|
DhcpSb->DhcpState = Dhcp4Init;
|
|
|
|
Status = DhcpInitRequest (DhcpSb);
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// Check whether the ACK matches the selected offer
|
|
//
|
|
if (EFI_NTOHL (Head->YourAddr) != DhcpSb->ClientAddr) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL);
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
//
|
|
// OK, get the parameter from server, record the lease
|
|
//
|
|
DhcpSb->Para = AllocateCopyPool (sizeof (DHCP_PARAMETER), Para);
|
|
if (DhcpSb->Para == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto ON_EXIT;
|
|
}
|
|
|
|
DhcpSb->Selected = Packet;
|
|
Status = DhcpLeaseAcquired (DhcpSb);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
DhcpSb->IoStatus = EFI_SUCCESS;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION);
|
|
return EFI_SUCCESS;
|
|
|
|
ON_EXIT:
|
|
FreePool (Packet);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Handle the received DHCP packets. This function drives the DHCP
|
|
state machine.
|
|
|
|
@param UdpPacket The UDP packets received.
|
|
@param EndPoint The local/remote UDP access point
|
|
@param IoStatus The status of the UDP receive
|
|
@param Context The opaque parameter to the function.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
DhcpInput (
|
|
NET_BUF *UdpPacket,
|
|
UDP_END_POINT *EndPoint,
|
|
EFI_STATUS IoStatus,
|
|
VOID *Context
|
|
)
|
|
{
|
|
DHCP_SERVICE *DhcpSb;
|
|
EFI_DHCP4_HEADER *Head;
|
|
EFI_DHCP4_PACKET *Packet;
|
|
DHCP_PARAMETER *Para;
|
|
EFI_STATUS Status;
|
|
UINT32 Len;
|
|
|
|
Packet = NULL;
|
|
DhcpSb = (DHCP_SERVICE *) Context;
|
|
|
|
//
|
|
// Don't restart receive if error occurs or DHCP is destoried.
|
|
//
|
|
if (EFI_ERROR (IoStatus)) {
|
|
return ;
|
|
} else if (DhcpSb->ServiceState == DHCP_DESTORY) {
|
|
NetbufFree (UdpPacket);
|
|
return ;
|
|
}
|
|
|
|
ASSERT (UdpPacket != NULL);
|
|
|
|
if (DhcpSb->DhcpState == Dhcp4Stopped) {
|
|
goto RESTART;
|
|
}
|
|
|
|
//
|
|
// Validate the packet received
|
|
//
|
|
if (UdpPacket->TotalSize < sizeof (EFI_DHCP4_HEADER)) {
|
|
goto RESTART;
|
|
}
|
|
|
|
//
|
|
// Copy the DHCP message to a continuous memory block
|
|
//
|
|
Len = sizeof (EFI_DHCP4_PACKET) + UdpPacket->TotalSize - sizeof (EFI_DHCP4_HEADER);
|
|
Packet = (EFI_DHCP4_PACKET *) AllocatePool (Len);
|
|
|
|
if (Packet == NULL) {
|
|
goto RESTART;
|
|
}
|
|
|
|
Packet->Size = Len;
|
|
Head = &Packet->Dhcp4.Header;
|
|
Packet->Length = NetbufCopy (UdpPacket, 0, UdpPacket->TotalSize, (UINT8 *) Head);
|
|
|
|
if (Packet->Length != UdpPacket->TotalSize) {
|
|
goto RESTART;
|
|
}
|
|
|
|
//
|
|
// Is this packet the answer to our packet?
|
|
//
|
|
if ((Head->OpCode != BOOTP_REPLY) ||
|
|
(NTOHL (Head->Xid) != DhcpSb->Xid) ||
|
|
(CompareMem (DhcpSb->ClientAddressSendOut, Head->ClientHwAddr, Head->HwAddrLen) != 0)) {
|
|
goto RESTART;
|
|
}
|
|
|
|
//
|
|
// Validate the options and retrieve the interested options
|
|
//
|
|
Para = NULL;
|
|
if ((Packet->Length > sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32)) &&
|
|
(Packet->Dhcp4.Magik == DHCP_OPTION_MAGIC) &&
|
|
EFI_ERROR (DhcpValidateOptions (Packet, &Para))) {
|
|
|
|
goto RESTART;
|
|
}
|
|
|
|
//
|
|
// Call the handler for each state. The handler should return
|
|
// EFI_SUCCESS if the process can go on no matter whether the
|
|
// packet is ignored or not. If the return is EFI_ERROR, the
|
|
// session will be terminated. Packet's ownership is handled
|
|
// over to the handlers. If operation succeeds, the handler
|
|
// must notify the user. It isn't necessary to do if EFI_ERROR
|
|
// is returned because the DhcpEndSession will notify the user.
|
|
//
|
|
Status = EFI_SUCCESS;
|
|
|
|
switch (DhcpSb->DhcpState) {
|
|
case Dhcp4Selecting:
|
|
Status = DhcpHandleSelect (DhcpSb, Packet, Para);
|
|
break;
|
|
|
|
case Dhcp4Requesting:
|
|
Status = DhcpHandleRequest (DhcpSb, Packet, Para);
|
|
break;
|
|
|
|
case Dhcp4InitReboot:
|
|
case Dhcp4Init:
|
|
case Dhcp4Bound:
|
|
//
|
|
// Ignore the packet in INITREBOOT, INIT and BOUND states
|
|
//
|
|
FreePool (Packet);
|
|
Status = EFI_SUCCESS;
|
|
break;
|
|
|
|
case Dhcp4Renewing:
|
|
case Dhcp4Rebinding:
|
|
Status = DhcpHandleRenewRebind (DhcpSb, Packet, Para);
|
|
break;
|
|
|
|
case Dhcp4Rebooting:
|
|
Status = DhcpHandleReboot (DhcpSb, Packet, Para);
|
|
break;
|
|
}
|
|
|
|
if (Para != NULL) {
|
|
FreePool (Para);
|
|
}
|
|
|
|
Packet = NULL;
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
NetbufFree (UdpPacket);
|
|
UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0);
|
|
DhcpEndSession (DhcpSb, Status);
|
|
return ;
|
|
}
|
|
|
|
RESTART:
|
|
NetbufFree (UdpPacket);
|
|
|
|
if (Packet != NULL) {
|
|
FreePool (Packet);
|
|
}
|
|
|
|
Status = UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DhcpEndSession (DhcpSb, EFI_DEVICE_ERROR);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Release the packet.
|
|
|
|
@param[in] Arg The packet to release
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
DhcpReleasePacket (
|
|
IN VOID *Arg
|
|
)
|
|
{
|
|
FreePool (Arg);
|
|
}
|
|
|
|
|
|
/**
|
|
Release the net buffer when packet is sent.
|
|
|
|
@param UdpPacket The UDP packets received.
|
|
@param EndPoint The local/remote UDP access point
|
|
@param IoStatus The status of the UDP receive
|
|
@param Context The opaque parameter to the function.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
DhcpOnPacketSent (
|
|
NET_BUF *Packet,
|
|
UDP_END_POINT *EndPoint,
|
|
EFI_STATUS IoStatus,
|
|
VOID *Context
|
|
)
|
|
{
|
|
NetbufFree (Packet);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Build and transmit a DHCP message according to the current states.
|
|
This function implement the Table 5. of RFC 2131. Always transits
|
|
the state (as defined in Figure 5. of the same RFC) before sending
|
|
a DHCP message. The table is adjusted accordingly.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
@param[in] Seed The seed packet which the new packet is based on
|
|
@param[in] Para The DHCP parameter of the Seed packet
|
|
@param[in] Type The message type to send
|
|
@param[in] Msg The human readable message to include in the packet
|
|
sent.
|
|
|
|
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources for the packet
|
|
@retval EFI_ACCESS_DENIED Failed to transmit the packet through UDP
|
|
@retval EFI_SUCCESS The message is sent
|
|
@retval other Other error occurs
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpSendMessage (
|
|
IN DHCP_SERVICE *DhcpSb,
|
|
IN EFI_DHCP4_PACKET *Seed,
|
|
IN DHCP_PARAMETER *Para,
|
|
IN UINT8 Type,
|
|
IN UINT8 *Msg
|
|
)
|
|
{
|
|
EFI_DHCP4_CONFIG_DATA *Config;
|
|
EFI_DHCP4_PACKET *Packet;
|
|
EFI_DHCP4_PACKET *NewPacket;
|
|
EFI_DHCP4_HEADER *Head;
|
|
EFI_DHCP4_HEADER *SeedHead;
|
|
UDP_IO *UdpIo;
|
|
UDP_END_POINT EndPoint;
|
|
NET_BUF *Wrap;
|
|
NET_FRAGMENT Frag;
|
|
EFI_STATUS Status;
|
|
IP4_ADDR IpAddr;
|
|
UINT8 *Buf;
|
|
UINT16 MaxMsg;
|
|
UINT32 Len;
|
|
UINT32 Index;
|
|
|
|
//
|
|
// Allocate a big enough memory block to hold the DHCP packet
|
|
//
|
|
Len = sizeof (EFI_DHCP4_PACKET) + 128 + DhcpSb->UserOptionLen;
|
|
|
|
if (Msg != NULL) {
|
|
Len += (UINT32)AsciiStrLen ((CHAR8 *) Msg);
|
|
}
|
|
|
|
Packet = AllocatePool (Len);
|
|
|
|
if (Packet == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Packet->Size = Len;
|
|
Packet->Length = sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32);
|
|
|
|
//
|
|
// Fill in the DHCP header fields
|
|
//
|
|
Config = &DhcpSb->ActiveConfig;
|
|
SeedHead = NULL;
|
|
|
|
if (Seed != NULL) {
|
|
SeedHead = &Seed->Dhcp4.Header;
|
|
}
|
|
|
|
Head = &Packet->Dhcp4.Header;
|
|
ZeroMem (Head, sizeof (EFI_DHCP4_HEADER));
|
|
|
|
Head->OpCode = BOOTP_REQUEST;
|
|
Head->HwType = DhcpSb->HwType;
|
|
Head->HwAddrLen = DhcpSb->HwLen;
|
|
Head->Xid = HTONL (DhcpSb->Xid);
|
|
Head->Reserved = HTONS (0x8000); //Server, broadcast the message please.
|
|
|
|
EFI_IP4 (Head->ClientAddr) = HTONL (DhcpSb->ClientAddr);
|
|
CopyMem (Head->ClientHwAddr, DhcpSb->Mac.Addr, DhcpSb->HwLen);
|
|
|
|
//
|
|
// Append the DHCP message type
|
|
//
|
|
Packet->Dhcp4.Magik = DHCP_OPTION_MAGIC;
|
|
Buf = Packet->Dhcp4.Option;
|
|
Buf = DhcpAppendOption (Buf, DHCP_TAG_TYPE, 1, &Type);
|
|
|
|
//
|
|
// Append the serverid option if necessary:
|
|
// 1. DHCP decline message
|
|
// 2. DHCP release message
|
|
// 3. DHCP request to confirm one lease.
|
|
//
|
|
if ((Type == DHCP_MSG_DECLINE) || (Type == DHCP_MSG_RELEASE) ||
|
|
((Type == DHCP_MSG_REQUEST) && (DhcpSb->DhcpState == Dhcp4Requesting))
|
|
) {
|
|
|
|
ASSERT ((Para != NULL) && (Para->ServerId != 0));
|
|
|
|
IpAddr = HTONL (Para->ServerId);
|
|
Buf = DhcpAppendOption (Buf, DHCP_TAG_SERVER_ID, 4, (UINT8 *) &IpAddr);
|
|
}
|
|
|
|
//
|
|
// Append the requested IP option if necessary:
|
|
// 1. DHCP request to use the previously allocated address
|
|
// 2. DHCP request to confirm one lease
|
|
// 3. DHCP decline to decline one lease
|
|
//
|
|
IpAddr = 0;
|
|
|
|
if (Type == DHCP_MSG_REQUEST) {
|
|
if (DhcpSb->DhcpState == Dhcp4Rebooting) {
|
|
IpAddr = EFI_IP4 (Config->ClientAddress);
|
|
|
|
} else if (DhcpSb->DhcpState == Dhcp4Requesting) {
|
|
ASSERT (SeedHead != NULL);
|
|
IpAddr = EFI_IP4 (SeedHead->YourAddr);
|
|
}
|
|
|
|
} else if (Type == DHCP_MSG_DECLINE) {
|
|
ASSERT (SeedHead != NULL);
|
|
IpAddr = EFI_IP4 (SeedHead->YourAddr);
|
|
}
|
|
|
|
if (IpAddr != 0) {
|
|
Buf = DhcpAppendOption (Buf, DHCP_TAG_REQUEST_IP, 4, (UINT8 *) &IpAddr);
|
|
}
|
|
|
|
//
|
|
// Append the Max Message Length option if it isn't a DECLINE
|
|
// or RELEASE to direct the server use large messages instead of
|
|
// override the BOOTFILE and SERVER fields in the message head.
|
|
//
|
|
if ((Type != DHCP_MSG_DECLINE) && (Type != DHCP_MSG_RELEASE)) {
|
|
MaxMsg = HTONS (0xFF00);
|
|
Buf = DhcpAppendOption (Buf, DHCP_TAG_MAXMSG, 2, (UINT8 *) &MaxMsg);
|
|
}
|
|
|
|
//
|
|
// Append the user's message if it isn't NULL
|
|
//
|
|
if (Msg != NULL) {
|
|
Len = MIN ((UINT32) AsciiStrLen ((CHAR8 *) Msg), 255);
|
|
Buf = DhcpAppendOption (Buf, DHCP_TAG_MESSAGE, (UINT16) Len, Msg);
|
|
}
|
|
|
|
//
|
|
// Append the user configured options
|
|
//
|
|
if (DhcpSb->UserOptionLen != 0) {
|
|
for (Index = 0; Index < Config->OptionCount; Index++) {
|
|
//
|
|
// We can't use any option other than the client ID from user
|
|
// if it is a DHCP decline or DHCP release .
|
|
//
|
|
if (((Type == DHCP_MSG_DECLINE) || (Type == DHCP_MSG_RELEASE)) &&
|
|
(Config->OptionList[Index]->OpCode != DHCP_TAG_CLIENT_ID)) {
|
|
continue;
|
|
}
|
|
|
|
Buf = DhcpAppendOption (
|
|
Buf,
|
|
Config->OptionList[Index]->OpCode,
|
|
Config->OptionList[Index]->Length,
|
|
Config->OptionList[Index]->Data
|
|
);
|
|
}
|
|
}
|
|
|
|
*(Buf++) = DHCP_TAG_EOP;
|
|
Packet->Length += (UINT32) (Buf - Packet->Dhcp4.Option);
|
|
|
|
//
|
|
// OK, the message is built, call the user to override it.
|
|
//
|
|
Status = EFI_SUCCESS;
|
|
NewPacket = NULL;
|
|
|
|
if (Type == DHCP_MSG_DISCOVER) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4SendDiscover, Packet, &NewPacket);
|
|
|
|
} else if (Type == DHCP_MSG_REQUEST) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4SendRequest, Packet, &NewPacket);
|
|
|
|
} else if (Type == DHCP_MSG_DECLINE) {
|
|
Status = DhcpCallUser (DhcpSb, Dhcp4SendDecline, Packet, &NewPacket);
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
FreePool (Packet);
|
|
return Status;
|
|
}
|
|
|
|
if (NewPacket != NULL) {
|
|
FreePool (Packet);
|
|
Packet = NewPacket;
|
|
}
|
|
|
|
//
|
|
// Save the Client Address will be sent out
|
|
//
|
|
CopyMem (
|
|
&DhcpSb->ClientAddressSendOut[0],
|
|
&Packet->Dhcp4.Header.ClientHwAddr[0],
|
|
Packet->Dhcp4.Header.HwAddrLen
|
|
);
|
|
|
|
|
|
//
|
|
// Wrap it into a netbuf then send it.
|
|
//
|
|
Frag.Bulk = (UINT8 *) &Packet->Dhcp4.Header;
|
|
Frag.Len = Packet->Length;
|
|
Wrap = NetbufFromExt (&Frag, 1, 0, 0, DhcpReleasePacket, Packet);
|
|
|
|
if (Wrap == NULL) {
|
|
FreePool (Packet);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Save it as the last sent packet for retransmission
|
|
//
|
|
if (DhcpSb->LastPacket != NULL) {
|
|
FreePool (DhcpSb->LastPacket);
|
|
}
|
|
|
|
DhcpSb->LastPacket = Packet;
|
|
DhcpSetTransmitTimer (DhcpSb);
|
|
|
|
//
|
|
// Broadcast the message, unless we know the server address.
|
|
// Use the lease UdpIo port to send the unicast packet.
|
|
//
|
|
EndPoint.RemoteAddr.Addr[0] = 0xffffffff;
|
|
EndPoint.LocalAddr.Addr[0] = 0;
|
|
EndPoint.RemotePort = DHCP_SERVER_PORT;
|
|
EndPoint.LocalPort = DHCP_CLIENT_PORT;
|
|
UdpIo = DhcpSb->UdpIo;
|
|
|
|
if ((DhcpSb->DhcpState == Dhcp4Renewing) || (Type == DHCP_MSG_RELEASE)) {
|
|
EndPoint.RemoteAddr.Addr[0] = DhcpSb->ServerAddr;
|
|
EndPoint.LocalAddr.Addr[0] = DhcpSb->ClientAddr;
|
|
UdpIo = DhcpSb->LeaseIoPort;
|
|
}
|
|
|
|
ASSERT (UdpIo != NULL);
|
|
NET_GET_REF (Wrap);
|
|
|
|
Status = UdpIoSendDatagram (
|
|
UdpIo,
|
|
Wrap,
|
|
&EndPoint,
|
|
NULL,
|
|
DhcpOnPacketSent,
|
|
DhcpSb
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
NET_PUT_REF (Wrap);
|
|
return EFI_ACCESS_DENIED;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Retransmit a saved packet. Only DISCOVER and REQUEST messages
|
|
will be retransmitted.
|
|
|
|
@param[in] DhcpSb The DHCP service instance
|
|
|
|
@retval EFI_ACCESS_DENIED Failed to transmit packet through UDP port
|
|
@retval EFI_SUCCESS The packet is retransmitted.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DhcpRetransmit (
|
|
IN DHCP_SERVICE *DhcpSb
|
|
)
|
|
{
|
|
UDP_IO *UdpIo;
|
|
UDP_END_POINT EndPoint;
|
|
NET_BUF *Wrap;
|
|
NET_FRAGMENT Frag;
|
|
EFI_STATUS Status;
|
|
|
|
ASSERT (DhcpSb->LastPacket != NULL);
|
|
|
|
DhcpSb->LastPacket->Dhcp4.Header.Seconds = HTONS (*(UINT16 *)(&DhcpSb->LastTimeout));
|
|
|
|
//
|
|
// Wrap it into a netbuf then send it.
|
|
//
|
|
Frag.Bulk = (UINT8 *) &DhcpSb->LastPacket->Dhcp4.Header;
|
|
Frag.Len = DhcpSb->LastPacket->Length;
|
|
Wrap = NetbufFromExt (&Frag, 1, 0, 0, DhcpReleasePacket, DhcpSb->LastPacket);
|
|
|
|
if (Wrap == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Broadcast the message, unless we know the server address.
|
|
//
|
|
EndPoint.RemotePort = DHCP_SERVER_PORT;
|
|
EndPoint.LocalPort = DHCP_CLIENT_PORT;
|
|
EndPoint.RemoteAddr.Addr[0] = 0xffffffff;
|
|
EndPoint.LocalAddr.Addr[0] = 0;
|
|
UdpIo = DhcpSb->UdpIo;
|
|
|
|
if (DhcpSb->DhcpState == Dhcp4Renewing) {
|
|
EndPoint.RemoteAddr.Addr[0] = DhcpSb->ServerAddr;
|
|
EndPoint.LocalAddr.Addr[0] = DhcpSb->ClientAddr;
|
|
UdpIo = DhcpSb->LeaseIoPort;
|
|
}
|
|
|
|
ASSERT (UdpIo != NULL);
|
|
|
|
NET_GET_REF (Wrap);
|
|
Status = UdpIoSendDatagram (
|
|
UdpIo,
|
|
Wrap,
|
|
&EndPoint,
|
|
NULL,
|
|
DhcpOnPacketSent,
|
|
DhcpSb
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
NET_PUT_REF (Wrap);
|
|
return EFI_ACCESS_DENIED;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Each DHCP service has three timer. Two of them are count down timer.
|
|
One for the packet retransmission. The other is to collect the offers.
|
|
The third timer increaments the lease life which is compared to T1, T2,
|
|
and lease to determine the time to renew and rebind the lease.
|
|
DhcpOnTimerTick will be called once every second.
|
|
|
|
@param[in] Event The timer event
|
|
@param[in] Context The context, which is the DHCP service instance.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
DhcpOnTimerTick (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
DHCP_SERVICE *DhcpSb;
|
|
DHCP_PROTOCOL *Instance;
|
|
EFI_STATUS Status;
|
|
|
|
DhcpSb = (DHCP_SERVICE *) Context;
|
|
Instance = DhcpSb->ActiveChild;
|
|
|
|
//
|
|
// Check the retransmit timer
|
|
//
|
|
if ((DhcpSb->PacketToLive > 0) && (--DhcpSb->PacketToLive == 0)) {
|
|
|
|
//
|
|
// Select offer at each timeout if any offer received.
|
|
//
|
|
if (DhcpSb->DhcpState == Dhcp4Selecting && DhcpSb->LastOffer != NULL) {
|
|
|
|
Status = DhcpChooseOffer (DhcpSb);
|
|
|
|
if (EFI_ERROR(Status)) {
|
|
if (DhcpSb->LastOffer != NULL) {
|
|
FreePool (DhcpSb->LastOffer);
|
|
DhcpSb->LastOffer = NULL;
|
|
}
|
|
} else {
|
|
goto ON_EXIT;
|
|
}
|
|
}
|
|
|
|
if (++DhcpSb->CurRetry < DhcpSb->MaxRetries) {
|
|
//
|
|
// Still has another try
|
|
//
|
|
DhcpRetransmit (DhcpSb);
|
|
DhcpSetTransmitTimer (DhcpSb);
|
|
|
|
} else if (DHCP_CONNECTED (DhcpSb->DhcpState)) {
|
|
|
|
//
|
|
// Retransmission failed, if the DHCP request is initiated by
|
|
// user, adjust the current state according to the lease life.
|
|
// Otherwise do nothing to wait the lease to timeout
|
|
//
|
|
if (DhcpSb->ExtraRefresh != 0) {
|
|
Status = EFI_SUCCESS;
|
|
|
|
if (DhcpSb->LeaseLife < DhcpSb->T1) {
|
|
Status = DhcpSetState (DhcpSb, Dhcp4Bound, FALSE);
|
|
|
|
} else if (DhcpSb->LeaseLife < DhcpSb->T2) {
|
|
Status = DhcpSetState (DhcpSb, Dhcp4Renewing, FALSE);
|
|
|
|
} else if (DhcpSb->LeaseLife < DhcpSb->Lease) {
|
|
Status = DhcpSetState (DhcpSb, Dhcp4Rebinding, FALSE);
|
|
|
|
} else {
|
|
goto END_SESSION;
|
|
|
|
}
|
|
|
|
DhcpSb->IoStatus = EFI_TIMEOUT;
|
|
DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_RENEWREBIND);
|
|
}
|
|
} else {
|
|
goto END_SESSION;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If an address has been acquired, check whether need to
|
|
// refresh or whether it has expired.
|
|
//
|
|
if (DHCP_CONNECTED (DhcpSb->DhcpState)) {
|
|
DhcpSb->LeaseLife++;
|
|
|
|
//
|
|
// Don't timeout the lease, only count the life if user is
|
|
// requesting extra renew/rebind. Adjust the state after that.
|
|
//
|
|
if (DhcpSb->ExtraRefresh != 0) {
|
|
return ;
|
|
}
|
|
|
|
if (DhcpSb->LeaseLife == DhcpSb->Lease) {
|
|
//
|
|
// Lease expires, end the session
|
|
//
|
|
goto END_SESSION;
|
|
|
|
} else if (DhcpSb->LeaseLife == DhcpSb->T2) {
|
|
//
|
|
// T2 expires, transit to rebinding then send a REQUEST to any server
|
|
//
|
|
if (EFI_ERROR (DhcpSetState (DhcpSb, Dhcp4Rebinding, TRUE))) {
|
|
goto END_SESSION;
|
|
}
|
|
|
|
Status = DhcpSendMessage (
|
|
DhcpSb,
|
|
DhcpSb->Selected,
|
|
DhcpSb->Para,
|
|
DHCP_MSG_REQUEST,
|
|
NULL
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto END_SESSION;
|
|
}
|
|
|
|
} else if (DhcpSb->LeaseLife == DhcpSb->T1) {
|
|
//
|
|
// T1 expires, transit to renewing, then send a REQUEST to the server
|
|
//
|
|
if (EFI_ERROR (DhcpSetState (DhcpSb, Dhcp4Renewing, TRUE))) {
|
|
goto END_SESSION;
|
|
}
|
|
|
|
Status = DhcpSendMessage (
|
|
DhcpSb,
|
|
DhcpSb->Selected,
|
|
DhcpSb->Para,
|
|
DHCP_MSG_REQUEST,
|
|
NULL
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto END_SESSION;
|
|
}
|
|
}
|
|
}
|
|
|
|
ON_EXIT:
|
|
if ((Instance != NULL) && (Instance->Token != NULL)) {
|
|
Instance->Timeout--;
|
|
if (Instance->Timeout == 0) {
|
|
PxeDhcpDone (Instance);
|
|
}
|
|
}
|
|
|
|
return ;
|
|
|
|
END_SESSION:
|
|
DhcpEndSession (DhcpSb, EFI_TIMEOUT);
|
|
|
|
return ;
|
|
}
|