blob: 5cdeac6d5cfe30bfbe67e1e685323c185361a76e [file] [log] [blame]
/** @file
Dhcp6 internal functions implementation.
(C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
Copyright (c) 2009 - 2016, 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 "Dhcp6Impl.h"
/**
Enqueue the packet into the retry list in case of timeout.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to the Dhcp6 packet to retry.
@param[in] Elapsed The pointer to the elapsed time value in the packet.
@param[in] RetryCtl The pointer to the transmission control of the packet.
This parameter is optional and may be NULL.
@retval EFI_SUCCESS Successfully enqueued the packet into the retry list according
to its message type.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected message type.
**/
EFI_STATUS
Dhcp6EnqueueRetry (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet,
IN UINT16 *Elapsed,
IN EFI_DHCP6_RETRANSMISSION *RetryCtl OPTIONAL
)
{
DHCP6_TX_CB *TxCb;
DHCP6_IA_CB *IaCb;
ASSERT (Packet != NULL);
IaCb = &Instance->IaCb;
TxCb = AllocateZeroPool (sizeof (DHCP6_TX_CB));
if (TxCb == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Save tx packet pointer, and it will be destroyed when reply received.
//
TxCb->TxPacket = Packet;
TxCb->Xid = Packet->Dhcp6.Header.TransactionId;
//
// Save pointer to elapsed-time value so we can update it on retransmits.
//
TxCb->Elapsed = Elapsed;
//
// Calculate the retransmission according to the the message type.
//
switch (Packet->Dhcp6.Header.MessageType) {
case Dhcp6MsgSolicit:
//
// Calculate the retransmission threshold value for solicit packet.
// Use the default value by rfc-3315 if user doesn't configure.
//
if (RetryCtl == NULL) {
TxCb->RetryCtl.Irt = DHCP6_SOL_IRT;
TxCb->RetryCtl.Mrc = DHCP6_SOL_MRC;
TxCb->RetryCtl.Mrt = DHCP6_SOL_MRT;
TxCb->RetryCtl.Mrd = DHCP6_SOL_MRD;
} else {
TxCb->RetryCtl.Irt = (RetryCtl->Irt != 0) ? RetryCtl->Irt : DHCP6_SOL_IRT;
TxCb->RetryCtl.Mrc = (RetryCtl->Mrc != 0) ? RetryCtl->Mrc : DHCP6_SOL_MRC;
TxCb->RetryCtl.Mrt = (RetryCtl->Mrt != 0) ? RetryCtl->Mrt : DHCP6_SOL_MRT;
TxCb->RetryCtl.Mrd = (RetryCtl->Mrd != 0) ? RetryCtl->Mrd : DHCP6_SOL_MRD;
}
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
FALSE
);
break;
case Dhcp6MsgRequest:
//
// Calculate the retransmission threshold value for request packet.
//
TxCb->RetryCtl.Irt = DHCP6_REQ_IRT;
TxCb->RetryCtl.Mrc = DHCP6_REQ_MRC;
TxCb->RetryCtl.Mrt = DHCP6_REQ_MRT;
TxCb->RetryCtl.Mrd = DHCP6_REQ_MRD;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgConfirm:
//
// Calculate the retransmission threshold value for confirm packet.
//
TxCb->RetryCtl.Irt = DHCP6_CNF_IRT;
TxCb->RetryCtl.Mrc = DHCP6_CNF_MRC;
TxCb->RetryCtl.Mrt = DHCP6_CNF_MRT;
TxCb->RetryCtl.Mrd = DHCP6_CNF_MRD;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgRenew:
//
// Calculate the retransmission threshold value for renew packet.
//
TxCb->RetryCtl.Irt = DHCP6_REB_IRT;
TxCb->RetryCtl.Mrc = DHCP6_REB_MRC;
TxCb->RetryCtl.Mrt = DHCP6_REB_MRT;
TxCb->RetryCtl.Mrd = IaCb->T2 - IaCb->T1;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgRebind:
//
// Calculate the retransmission threshold value for rebind packet.
//
TxCb->RetryCtl.Irt = DHCP6_REN_IRT;
TxCb->RetryCtl.Mrc = DHCP6_REN_MRC;
TxCb->RetryCtl.Mrt = DHCP6_REN_MRT;
TxCb->RetryCtl.Mrd = IaCb->AllExpireTime - IaCb->T2;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgDecline:
//
// Calculate the retransmission threshold value for decline packet.
//
TxCb->RetryCtl.Irt = DHCP6_DEC_IRT;
TxCb->RetryCtl.Mrc = DHCP6_DEC_MRC;
TxCb->RetryCtl.Mrt = DHCP6_DEC_MRT;
TxCb->RetryCtl.Mrd = DHCP6_DEC_MRD;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgRelease:
//
// Calculate the retransmission threshold value for release packet.
//
TxCb->RetryCtl.Irt = DHCP6_REL_IRT;
TxCb->RetryCtl.Mrc = DHCP6_REL_MRC;
TxCb->RetryCtl.Mrt = DHCP6_REL_MRT;
TxCb->RetryCtl.Mrd = DHCP6_REL_MRD;
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
case Dhcp6MsgInfoRequest:
//
// Calculate the retransmission threshold value for info-request packet.
// Use the default value by rfc-3315 if user doesn't configure.
//
if (RetryCtl == NULL) {
TxCb->RetryCtl.Irt = DHCP6_INF_IRT;
TxCb->RetryCtl.Mrc = DHCP6_INF_MRC;
TxCb->RetryCtl.Mrt = DHCP6_INF_MRT;
TxCb->RetryCtl.Mrd = DHCP6_INF_MRD;
} else {
TxCb->RetryCtl.Irt = (RetryCtl->Irt != 0) ? RetryCtl->Irt : DHCP6_INF_IRT;
TxCb->RetryCtl.Mrc = (RetryCtl->Mrc != 0) ? RetryCtl->Mrc : DHCP6_INF_MRC;
TxCb->RetryCtl.Mrt = (RetryCtl->Mrt != 0) ? RetryCtl->Mrt : DHCP6_INF_MRT;
TxCb->RetryCtl.Mrd = (RetryCtl->Mrd != 0) ? RetryCtl->Mrd : DHCP6_INF_MRD;
}
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Irt,
TRUE,
TRUE
);
break;
default:
//
// Unexpected message type.
//
return EFI_DEVICE_ERROR;
}
//
// Insert into the retransmit list of the instance.
//
InsertTailList (&Instance->TxList, &TxCb->Link);
return EFI_SUCCESS;
}
/**
Dequeue the packet from retry list if reply received or timeout at last.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] PacketXid The packet transaction id to match.
@param[in] NeedSignal If TRUE, then an timeout event need be signaled when it is existed.
Otherwise, this parameter is ignored.
@retval EFI_SUCCESS Successfully dequeued the packet into retry list .
@retval EFI_NOT_FOUND There is no xid matched in retry list.
**/
EFI_STATUS
Dhcp6DequeueRetry (
IN DHCP6_INSTANCE *Instance,
IN UINT32 PacketXid,
IN BOOLEAN NeedSignal
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
DHCP6_TX_CB *TxCb;
DHCP6_INF_CB *InfCb;
//
// Seek the retransmit node in the retransmit list by packet xid.
//
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
ASSERT(TxCb->TxPacket);
if (TxCb->Xid == PacketXid) {
if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
//
// Seek the info-request node in the info-request list by packet xid.
//
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->InfList) {
InfCb = NET_LIST_USER_STRUCT (Entry, DHCP6_INF_CB, Link);
if (InfCb->Xid == PacketXid) {
//
// Remove the info-request node, and signal the event if timeout.
//
if (InfCb->TimeoutEvent != NULL && NeedSignal) {
gBS->SignalEvent (InfCb->TimeoutEvent);
}
RemoveEntryList (&InfCb->Link);
FreePool (InfCb);
}
}
}
//
// Remove the retransmit node.
//
RemoveEntryList (&TxCb->Link);
ASSERT(TxCb->TxPacket);
FreePool (TxCb->TxPacket);
FreePool (TxCb);
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
/**
Clean up the specific nodes in the retry list.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Scope The scope of cleanup nodes.
**/
VOID
Dhcp6CleanupRetry (
IN DHCP6_INSTANCE *Instance,
IN UINT32 Scope
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
DHCP6_TX_CB *TxCb;
DHCP6_INF_CB *InfCb;
//
// Clean up all the stateful messages from the retransmit list.
//
if (Scope == DHCP6_PACKET_STATEFUL || Scope == DHCP6_PACKET_ALL) {
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
ASSERT(TxCb->TxPacket);
if (TxCb->TxPacket->Dhcp6.Header.MessageType != Dhcp6MsgInfoRequest) {
RemoveEntryList (&TxCb->Link);
FreePool (TxCb->TxPacket);
FreePool (TxCb);
}
}
}
//
// Clean up all the stateless messages from the retransmit list.
//
if (Scope == DHCP6_PACKET_STATELESS || Scope == DHCP6_PACKET_ALL) {
//
// Clean up all the retransmit list for stateless messages.
//
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
ASSERT(TxCb->TxPacket);
if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
RemoveEntryList (&TxCb->Link);
FreePool (TxCb->TxPacket);
FreePool (TxCb);
}
}
//
// Clean up all the info-request messages list.
//
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->InfList) {
InfCb = NET_LIST_USER_STRUCT (Entry, DHCP6_INF_CB, Link);
if (InfCb->TimeoutEvent != NULL) {
gBS->SignalEvent (InfCb->TimeoutEvent);
}
RemoveEntryList (&InfCb->Link);
FreePool (InfCb);
}
}
}
/**
Check whether the TxCb is still a valid control block in the instance's retry list.
@param[in] Instance The pointer to DHCP6_INSTANCE.
@param[in] TxCb The control block for a transmitted message.
@retval TRUE The control block is in Instance's retry list.
@retval FALSE The control block is NOT in Instance's retry list.
**/
BOOLEAN
Dhcp6IsValidTxCb (
IN DHCP6_INSTANCE *Instance,
IN DHCP6_TX_CB *TxCb
)
{
LIST_ENTRY *Entry;
NET_LIST_FOR_EACH (Entry, &Instance->TxList) {
if (TxCb == NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link)) {
return TRUE;
}
}
return FALSE;
}
/**
Clean up the session of the instance stateful exchange.
@param[in, out] Instance The pointer to the Dhcp6 instance.
@param[in] Status The return status from udp.
**/
VOID
Dhcp6CleanupSession (
IN OUT DHCP6_INSTANCE *Instance,
IN EFI_STATUS Status
)
{
UINTN Index;
EFI_DHCP6_IA *Ia;
ASSERT(Instance->Config);
ASSERT(Instance->IaCb.Ia);
//
// Clean up the retransmit list for stateful messages.
//
Dhcp6CleanupRetry (Instance, DHCP6_PACKET_STATEFUL);
if (Instance->Unicast != NULL) {
FreePool (Instance->Unicast);
}
if (Instance->AdSelect != NULL) {
FreePool (Instance->AdSelect);
}
if (Instance->IaCb.Ia->ReplyPacket != NULL) {
FreePool (Instance->IaCb.Ia->ReplyPacket);
}
//
// Reinitialize the Ia fields of the instance.
//
Instance->UdpSts = Status;
Instance->AdSelect = NULL;
Instance->AdPref = 0;
Instance->Unicast = NULL;
Instance->IaCb.T1 = 0;
Instance->IaCb.T2 = 0;
Instance->IaCb.AllExpireTime = 0;
Instance->IaCb.LeaseTime = 0;
//
// Clear start time
//
Instance->StartTime = 0;
Ia = Instance->IaCb.Ia;
Ia->State = Dhcp6Init;
Ia->ReplyPacket = NULL;
//
// Set the addresses as zero lifetime, and then the notify
// function in Ip6Config will remove these timeout address.
//
for (Index = 0; Index < Ia->IaAddressCount; Index++) {
Ia->IaAddress[Index].PreferredLifetime = 0;
Ia->IaAddress[Index].ValidLifetime = 0;
}
//
//
// Signal the Ia information updated event to informal user.
//
if (Instance->Config->IaInfoEvent != NULL) {
gBS->SignalEvent (Instance->Config->IaInfoEvent);
}
}
/**
Callback to user when Dhcp6 transmit/receive occurs.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Event The current Dhcp6 event.
@param[in, out] Packet The pointer to the packet sending or received.
@retval EFI_SUCCESS The user function returns success.
@retval EFI_NOT_READY Direct the caller to continue collecting the offer.
@retval EFI_ABORTED The user function ask it to abort.
**/
EFI_STATUS
EFIAPI
Dhcp6CallbackUser (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_EVENT Event,
IN OUT EFI_DHCP6_PACKET **Packet
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *NewPacket;
EFI_DHCP6_CALLBACK Callback;
VOID *Context;
ASSERT (Packet != NULL);
ASSERT (Instance->Config != NULL);
ASSERT (Instance->IaCb.Ia != NULL);
NewPacket = NULL;
Status = EFI_SUCCESS;
Callback = Instance->Config->Dhcp6Callback;
Context = Instance->Config->CallbackContext;
//
// Callback to user with the new message if has.
//
if (Callback != NULL) {
Status = Callback (
&Instance->Dhcp6,
Context,
Instance->IaCb.Ia->State,
Event,
*Packet,
&NewPacket
);
//
// Updated the new packet from user to replace the original one.
//
if (NewPacket != NULL) {
ASSERT (*Packet != NULL);
FreePool (*Packet);
*Packet = NewPacket;
}
}
return Status;
}
/**
Update Ia according to the new reply message.
@param[in, out] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to reply messages.
@retval EFI_SUCCESS Updated the Ia information successfully.
@retval EFI_DEVICE_ERROR An unexpected error.
**/
EFI_STATUS
Dhcp6UpdateIaInfo (
IN OUT DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
UINT8 *Option;
UINT8 *IaInnerOpt;
UINT16 IaInnerLen;
UINT16 StsCode;
UINT32 T1;
UINT32 T2;
ASSERT (Instance->Config != NULL);
//
// If the reply was received in reponse to a solicit with rapid commit option,
// request, renew or rebind message, the client updates the information it has
// recorded about IAs from the IA options contained in the reply message:
// 1. record the T1 and T2 times
// 2. add any new addresses in the IA
// 3. discard any addresses from the IA, that have a valid lifetime of 0
// 4. update lifetimes for any addresses that alread recorded
// 5. leave unchanged any information about addresses
//
// See details in the section-18.1.8 of rfc-3315.
//
Option = Dhcp6SeekIaOption (
Packet->Dhcp6.Option,
Packet->Length - sizeof (EFI_DHCP6_HEADER),
&Instance->Config->IaDescriptor
);
if (Option == NULL) {
return EFI_DEVICE_ERROR;
}
//
// The format of the IA_NA option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_IA_NA | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IAID (4 octets) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T2 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . IA_NA-options .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// The format of the IA_TA option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_IA_TA | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IAID (4 octets) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . IA_TA-options .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// sizeof (option-code + option-len + IaId) = 8
// sizeof (option-code + option-len + IaId + T1) = 12
// sizeof (option-code + option-len + IaId + T1 + T2) = 16
//
// The inner options still start with 2 bytes option-code and 2 bytes option-len.
//
if (Instance->Config->IaDescriptor.Type == Dhcp6OptIana) {
T1 = NTOHL (ReadUnaligned32 ((UINT32 *) (Option + 8)));
T2 = NTOHL (ReadUnaligned32 ((UINT32 *) (Option + 12)));
//
// Refer to RFC3155 Chapter 22.4. If a client receives an IA_NA with T1 greater than T2,
// and both T1 and T2 are greater than 0, the client discards the IA_NA option and processes
// the remainder of the message as though the server had not included the invalid IA_NA option.
//
if (T1 > T2 && T2 > 0) {
return EFI_DEVICE_ERROR;
}
IaInnerOpt = Option + 16;
IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 2))) - 12);
} else {
T1 = 0;
T2 = 0;
IaInnerOpt = Option + 8;
IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 2))) - 4);
}
//
// The format of the Status Code option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_STATUS_CODE | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | status-code | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
// . .
// . status-message .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// sizeof (option-code + option-len) = 4
//
StsCode = Dhcp6StsSuccess;
Option = Dhcp6SeekOption (IaInnerOpt, IaInnerLen, Dhcp6OptStatusCode);
if (Option != NULL) {
StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 4)));
if (StsCode != Dhcp6StsSuccess) {
return EFI_DEVICE_ERROR;
}
}
//
// Generate control block for the Ia.
//
Status = Dhcp6GenerateIaCb (
Instance,
IaInnerOpt,
IaInnerLen,
T1,
T2
);
return Status;
}
/**
Seek StatusCode Option in package. A Status Code option may appear in the
options field of a DHCP message and/or in the options field of another option.
See details in section 22.13, RFC3315.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to reply messages.
@param[out] Option The pointer to status code option.
@retval EFI_SUCCESS Seek status code option successfully.
@retval EFI_DEVICE_ERROR An unexpected error.
**/
EFI_STATUS
Dhcp6SeekStsOption (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet,
OUT UINT8 **Option
)
{
UINT8 *IaInnerOpt;
UINT16 IaInnerLen;
UINT16 StsCode;
//
// Seek StatusCode option directly in DHCP message body. That is, search in
// non-encapsulated option fields.
//
*Option = Dhcp6SeekOption (
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptStatusCode
);
if (*Option != NULL) {
StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 4)));
if (StsCode != Dhcp6StsSuccess) {
return EFI_DEVICE_ERROR;
}
}
//
// Seek in encapsulated options, IA_NA and IA_TA.
//
*Option = Dhcp6SeekIaOption (
Packet->Dhcp6.Option,
Packet->Length - sizeof (EFI_DHCP6_HEADER),
&Instance->Config->IaDescriptor
);
if (*Option == NULL) {
return EFI_SUCCESS;
}
//
// The format of the IA_NA option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_IA_NA | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IAID (4 octets) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T2 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . IA_NA-options .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// The format of the IA_TA option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_IA_TA | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IAID (4 octets) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . IA_TA-options .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// sizeof (option-code + option-len + IaId) = 8
// sizeof (option-code + option-len + IaId + T1) = 12
// sizeof (option-code + option-len + IaId + T1 + T2) = 16
//
// The inner options still start with 2 bytes option-code and 2 bytes option-len.
//
if (Instance->Config->IaDescriptor.Type == Dhcp6OptIana) {
IaInnerOpt = *Option + 16;
IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 2))) - 12);
} else {
IaInnerOpt = *Option + 8;
IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 2))) - 4);
}
//
// The format of the Status Code option is:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_STATUS_CODE | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | status-code | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
// . .
// . status-message .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// sizeof (option-code + option-len) = 4
//
*Option = Dhcp6SeekOption (IaInnerOpt, IaInnerLen, Dhcp6OptStatusCode);
if (*Option != NULL) {
StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 4)));
if (StsCode != Dhcp6StsSuccess) {
return EFI_DEVICE_ERROR;
}
}
return EFI_SUCCESS;
}
/**
Transmit Dhcp6 message by udpio.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to transmit message.
@param[in] Elapsed The pointer to the elapsed time value to fill in.
@retval EFI_SUCCESS Successfully transmitted the packet.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval Others Failed to transmit the packet.
**/
EFI_STATUS
Dhcp6TransmitPacket (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet,
IN UINT16 *Elapsed
)
{
EFI_STATUS Status;
NET_BUF *Wrap;
NET_FRAGMENT Frag;
UDP_END_POINT EndPt;
DHCP6_SERVICE *Service;
Service = Instance->Service;
//
// Wrap it into a netbuf then send it.
//
Frag.Bulk = (UINT8 *) &Packet->Dhcp6.Header;
Frag.Len = Packet->Length;
//
// Do not register free packet here, which will be handled in retry list.
//
Wrap = NetbufFromExt (&Frag, 1, 0, 0, Dhcp6DummyExtFree, NULL);
if (Wrap == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Multicast the Dhcp6 message, unless get the unicast server address by option.
//
ZeroMem (&EndPt, sizeof (UDP_END_POINT));
if (Instance->Unicast != NULL) {
CopyMem (
&EndPt.RemoteAddr,
Instance->Unicast,
sizeof (EFI_IPv6_ADDRESS)
);
} else {
CopyMem (
&EndPt.RemoteAddr,
&mAllDhcpRelayAndServersAddress,
sizeof (EFI_IPv6_ADDRESS)
);
}
EndPt.RemotePort = DHCP6_PORT_SERVER;
EndPt.LocalPort = DHCP6_PORT_CLIENT;
//
// Update the elapsed time value.
//
if (Elapsed != NULL) {
SetElapsedTime (Elapsed, Instance);
}
//
// Send out the message by the configured Udp6Io.
//
Status = UdpIoSendDatagram (
Service->UdpIo,
Wrap,
&EndPt,
NULL,
Dhcp6OnTransmitted,
NULL
);
if (EFI_ERROR (Status)) {
NetbufFree (Wrap);
return Status;
}
return EFI_SUCCESS;
}
/**
Create the solicit message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@retval EFI_SUCCESS Created and sent the solicit message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval Others Failed to send the solicit message.
**/
EFI_STATUS
Dhcp6SendSolicitMsg (
IN DHCP6_INSTANCE *Instance
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET_OPTION *UserOpt;
EFI_DHCP6_DUID *ClientId;
DHCP6_SERVICE *Service;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT32 UserLen;
UINTN Index;
UINT16 Length;
Service = Instance->Service;
ClientId = Service->ClientId;
UserLen = 0;
ASSERT (Service->ClientId != NULL);
ASSERT (Instance->Config != NULL);
ASSERT (Instance->IaCb.Ia != NULL);
//
// Calculate the added length of customized option list.
//
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
}
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE + UserLen;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgSolicit;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for solicit message.
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendIaOption (
Cursor,
Instance->IaCb.Ia,
Instance->IaCb.T1,
Instance->IaCb.T2,
Packet->Dhcp6.Header.MessageType
);
//
// Append user-defined when configurate Dhcp6 service.
//
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserOpt = Instance->Config->OptionList[Index];
Cursor = Dhcp6AppendOption(
Cursor,
UserOpt->OpCode,
UserOpt->OpLen,
UserOpt->Data
);
}
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SendSolicit, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send solicit packet with the state transition from Dhcp6init to
// Dhcp6selecting.
//
Instance->IaCb.Ia->State = Dhcp6Selecting;
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (
Instance,
Packet,
Elapsed,
Instance->Config->SolicitRetransmission
);
}
/**
Configure some parameter to initiate SolicitMsg.
@param[in] Instance The pointer to the Dhcp6 instance.
@retval EFI_SUCCESS Created and sent the solicit message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval Others Failed to send the solicit message.
**/
EFI_STATUS
Dhcp6InitSolicitMsg (
IN DHCP6_INSTANCE *Instance
)
{
Instance->IaCb.T1 = 0;
Instance->IaCb.T2 = 0;
Instance->IaCb.Ia->IaAddressCount = 0;
return Dhcp6SendSolicitMsg (Instance);
}
/**
Create the request message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@retval EFI_SUCCESS Created and sent the request message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to send the request message.
**/
EFI_STATUS
Dhcp6SendRequestMsg (
IN DHCP6_INSTANCE *Instance
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET_OPTION *UserOpt;
EFI_DHCP6_DUID *ClientId;
EFI_DHCP6_DUID *ServerId;
DHCP6_SERVICE *Service;
UINT8 *Option;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT32 UserLen;
UINTN Index;
UINT16 Length;
ASSERT(Instance->AdSelect != NULL);
ASSERT(Instance->Config != NULL);
ASSERT(Instance->IaCb.Ia != NULL);
ASSERT(Instance->Service != NULL);
Service = Instance->Service;
ClientId = Service->ClientId;
ASSERT(ClientId != NULL);
//
// Get the server Id from the selected advertisement message.
//
Option = Dhcp6SeekOption (
Instance->AdSelect->Dhcp6.Option,
Instance->AdSelect->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
return EFI_DEVICE_ERROR;
}
ServerId = (EFI_DHCP6_DUID *) (Option + 2);
//
// Calculate the added length of customized option list.
//
UserLen = 0;
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
}
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE + UserLen;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgRequest;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for request message.
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptServerId),
ServerId->Length,
ServerId->Duid
);
Cursor = Dhcp6AppendIaOption (
Cursor,
Instance->IaCb.Ia,
Instance->IaCb.T1,
Instance->IaCb.T2,
Packet->Dhcp6.Header.MessageType
);
//
// Append user-defined when configurate Dhcp6 service.
//
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserOpt = Instance->Config->OptionList[Index];
Cursor = Dhcp6AppendOption(
Cursor,
UserOpt->OpCode,
UserOpt->OpLen,
UserOpt->Data
);
}
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SendRequest, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send request packet with the state transition from Dhcp6selecting to
// Dhcp6requesting.
//
Instance->IaCb.Ia->State = Dhcp6Requesting;
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);
}
/**
Create the decline message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] DecIa The pointer to the decline Ia.
@retval EFI_SUCCESS Created and sent the decline message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to send the decline message.
**/
EFI_STATUS
Dhcp6SendDeclineMsg (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_IA *DecIa
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET *LastReply;
EFI_DHCP6_DUID *ClientId;
EFI_DHCP6_DUID *ServerId;
DHCP6_SERVICE *Service;
UINT8 *Option;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT16 Length;
ASSERT (Instance->Config != NULL);
ASSERT (Instance->IaCb.Ia != NULL);
ASSERT (Instance->Service != NULL);
Service = Instance->Service;
ClientId = Service->ClientId;
LastReply = Instance->IaCb.Ia->ReplyPacket;
ASSERT (ClientId != NULL);
ASSERT (LastReply != NULL);
//
// Get the server Id from the last reply message.
//
Option = Dhcp6SeekOption (
LastReply->Dhcp6.Option,
LastReply->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
return EFI_DEVICE_ERROR;
}
//
// EFI_DHCP6_DUID contains a length field of 2 bytes.
//
ServerId = (EFI_DHCP6_DUID *) (Option + 2);
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgDecline;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for rebind/renew message.
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptServerId),
ServerId->Length,
ServerId->Duid
);
Cursor = Dhcp6AppendIaOption (Cursor, DecIa, 0, 0, Packet->Dhcp6.Header.MessageType);
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SendDecline, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send decline packet with the state transition from Dhcp6bound to
// Dhcp6declining.
//
Instance->IaCb.Ia->State = Dhcp6Declining;
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);
}
/**
Create the release message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] RelIa The pointer to the release Ia.
@retval EFI_SUCCESS Created and sent the release message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to send the release message.
**/
EFI_STATUS
Dhcp6SendReleaseMsg (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_IA *RelIa
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET *LastReply;
EFI_DHCP6_DUID *ClientId;
EFI_DHCP6_DUID *ServerId;
DHCP6_SERVICE *Service;
UINT8 *Option;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT16 Length;
ASSERT(Instance->Config);
ASSERT(Instance->IaCb.Ia);
Service = Instance->Service;
ClientId = Service->ClientId;
LastReply = Instance->IaCb.Ia->ReplyPacket;
ASSERT(ClientId);
ASSERT(LastReply);
//
// Get the server Id from the last reply message.
//
Option = Dhcp6SeekOption (
LastReply->Dhcp6.Option,
LastReply->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
return EFI_DEVICE_ERROR;
}
ServerId = (EFI_DHCP6_DUID *) (Option + 2);
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgRelease;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for rebind/renew message
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
//
// ServerId is extracted from packet, it's network order.
//
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptServerId),
ServerId->Length,
ServerId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendIaOption (Cursor, RelIa, 0, 0, Packet->Dhcp6.Header.MessageType);
//
// Determine the size/length of packet
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SendRelease, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send release packet with the state transition from Dhcp6bound to
// Dhcp6releasing.
//
Instance->IaCb.Ia->State = Dhcp6Releasing;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);
}
/**
Create the renew/rebind message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] RebindRequest If TRUE, it is a Rebind type message.
Otherwise, it is a Renew type message.
@retval EFI_SUCCESS Created and sent the renew/rebind message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to send the renew/rebind message.
**/
EFI_STATUS
Dhcp6SendRenewRebindMsg (
IN DHCP6_INSTANCE *Instance,
IN BOOLEAN RebindRequest
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET *LastReply;
EFI_DHCP6_PACKET_OPTION *UserOpt;
EFI_DHCP6_DUID *ClientId;
EFI_DHCP6_DUID *ServerId;
EFI_DHCP6_STATE State;
EFI_DHCP6_EVENT Event;
DHCP6_SERVICE *Service;
UINT8 *Option;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT32 UserLen;
UINTN Index;
UINT16 Length;
ASSERT(Instance->Config);
ASSERT(Instance->IaCb.Ia);
Service = Instance->Service;
ClientId = Service->ClientId;
ASSERT(ClientId);
//
// Calculate the added length of customized option list.
//
UserLen = 0;
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
}
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE + UserLen;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = RebindRequest ? Dhcp6MsgRebind : Dhcp6MsgRenew;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for rebind/renew message.
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendIaOption (
Cursor,
Instance->IaCb.Ia,
Instance->IaCb.T1,
Instance->IaCb.T2,
Packet->Dhcp6.Header.MessageType
);
if (!RebindRequest) {
//
// Get the server Id from the last reply message and
// insert it for rebind request.
//
LastReply = Instance->IaCb.Ia->ReplyPacket;
ASSERT (LastReply);
Option = Dhcp6SeekOption (
LastReply->Dhcp6.Option,
LastReply->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
FreePool (Packet);
return EFI_DEVICE_ERROR;
}
ServerId = (EFI_DHCP6_DUID *) (Option + 2);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptServerId),
ServerId->Length,
ServerId->Duid
);
}
//
// Append user-defined when configurate Dhcp6 service.
//
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserOpt = Instance->Config->OptionList[Index];
Cursor = Dhcp6AppendOption(
Cursor,
UserOpt->OpCode,
UserOpt->OpLen,
UserOpt->Data
);
}
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
State = (RebindRequest) ? Dhcp6Rebinding : Dhcp6Renewing;
Event = (RebindRequest) ? Dhcp6EnterRebinding : Dhcp6EnterRenewing;
Status = Dhcp6CallbackUser (Instance, Event, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send renew/rebind packet with the state transition from Dhcp6bound to
// Dhcp6renew/rebind.
// And sync the lease time when send renew/rebind, in case that user send
// renew/rebind actively.
//
Instance->IaCb.Ia->State = State;
Instance->IaCb.LeaseTime = (RebindRequest) ? Instance->IaCb.T2 : Instance->IaCb.T1;
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);
}
/**
Start the information request process.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] SendClientId If TRUE, the client identifier option will be included in
information request message. Otherwise, the client identifier
option will not be included.
@param[in] OptionRequest The pointer to the option request option.
@param[in] OptionCount The number options in the OptionList.
@param[in] OptionList The array pointers to the appended options.
@param[in] Retransmission The pointer to the retransmission control.
@param[in] TimeoutEvent The event of timeout.
@param[in] ReplyCallback The callback function when the reply was received.
@param[in] CallbackContext The pointer to the parameter passed to the callback.
@retval EFI_SUCCESS Start the info-request process successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_NO_MAPPING No source address is available for use.
@retval Others Failed to start the info-request process.
**/
EFI_STATUS
Dhcp6StartInfoRequest (
IN DHCP6_INSTANCE *Instance,
IN BOOLEAN SendClientId,
IN EFI_DHCP6_PACKET_OPTION *OptionRequest,
IN UINT32 OptionCount,
IN EFI_DHCP6_PACKET_OPTION *OptionList[] OPTIONAL,
IN EFI_DHCP6_RETRANSMISSION *Retransmission,
IN EFI_EVENT TimeoutEvent OPTIONAL,
IN EFI_DHCP6_INFO_CALLBACK ReplyCallback,
IN VOID *CallbackContext OPTIONAL
)
{
EFI_STATUS Status;
DHCP6_INF_CB *InfCb;
DHCP6_SERVICE *Service;
EFI_TPL OldTpl;
Service = Instance->Service;
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
Instance->UdpSts = EFI_ALREADY_STARTED;
//
// Create and initialize the control block for the info-request.
//
InfCb = AllocateZeroPool (sizeof(DHCP6_INF_CB));
if (InfCb == NULL) {
gBS->RestoreTPL (OldTpl);
return EFI_OUT_OF_RESOURCES;
}
InfCb->ReplyCallback = ReplyCallback;
InfCb->CallbackContext = CallbackContext;
InfCb->TimeoutEvent = TimeoutEvent;
InsertTailList (&Instance->InfList, &InfCb->Link);
//
// Send the info-request message to start exchange process.
//
Status = Dhcp6SendInfoRequestMsg (
Instance,
InfCb,
SendClientId,
OptionRequest,
OptionCount,
OptionList,
Retransmission
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
//
// Register receive callback for the stateless exchange process.
//
Status = UdpIoRecvDatagram(
Service->UdpIo,
Dhcp6ReceivePacket,
Service,
0
);
if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
goto ON_ERROR;
}
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
ON_ERROR:
gBS->RestoreTPL (OldTpl);
RemoveEntryList (&InfCb->Link);
FreePool (InfCb);
return Status;
}
/**
Create the information request message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] InfCb The pointer to the information request control block.
@param[in] SendClientId If TRUE, the client identifier option will be included in
information request message. Otherwise, the client identifier
option will not be included.
@param[in] OptionRequest The pointer to the option request option.
@param[in] OptionCount The number options in the OptionList.
@param[in] OptionList The array pointers to the appended options.
@param[in] Retransmission The pointer to the retransmission control.
@retval EFI_SUCCESS Created and sent the info-request message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval Others Failed to send the info-request message.
**/
EFI_STATUS
Dhcp6SendInfoRequestMsg (
IN DHCP6_INSTANCE *Instance,
IN DHCP6_INF_CB *InfCb,
IN BOOLEAN SendClientId,
IN EFI_DHCP6_PACKET_OPTION *OptionRequest,
IN UINT32 OptionCount,
IN EFI_DHCP6_PACKET_OPTION *OptionList[],
IN EFI_DHCP6_RETRANSMISSION *Retransmission
)
{
EFI_STATUS Status;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET_OPTION *UserOpt;
EFI_DHCP6_DUID *ClientId;
DHCP6_SERVICE *Service;
UINT8 *Cursor;
UINT16 *Elapsed;
UINT32 UserLen;
UINTN Index;
UINT16 Length;
ASSERT(OptionRequest);
Service = Instance->Service;
ClientId = Service->ClientId;
UserLen = NTOHS (OptionRequest->OpLen) + 4;
ASSERT(ClientId);
//
// Calculate the added length of customized option list.
//
for (Index = 0; Index < OptionCount; Index++) {
UserLen += (NTOHS (OptionList[Index]->OpLen) + 4);
}
//
// Create the Dhcp6 packet and initialize commone fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE + UserLen;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgInfoRequest;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
InfCb->Xid = Packet->Dhcp6.Header.TransactionId;
//
// Assembly Dhcp6 options for info-request message.
//
Cursor = Packet->Dhcp6.Option;
if (SendClientId) {
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
}
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendOption (
Cursor,
OptionRequest->OpCode,
OptionRequest->OpLen,
OptionRequest->Data
);
//
// Append user-defined when configurate Dhcp6 service.
//
for (Index = 0; Index < OptionCount; Index++) {
UserOpt = OptionList[Index];
Cursor = Dhcp6AppendOption(
Cursor,
UserOpt->OpCode,
UserOpt->OpLen,
UserOpt->Data
);
}
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
//
// Send info-request packet with no state.
//
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, Retransmission);
}
/**
Create the Confirm message and send it.
@param[in] Instance The pointer to the Dhcp6 instance.
@retval EFI_SUCCESS Created and sent the confirm message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to send the confirm message.
**/
EFI_STATUS
Dhcp6SendConfirmMsg (
IN DHCP6_INSTANCE *Instance
)
{
UINT8 *Cursor;
UINTN Index;
UINT16 Length;
UINT32 UserLen;
EFI_STATUS Status;
DHCP6_SERVICE *Service;
EFI_DHCP6_DUID *ClientId;
EFI_DHCP6_PACKET *Packet;
EFI_DHCP6_PACKET_OPTION *UserOpt;
UINT16 *Elapsed;
ASSERT (Instance->Config != NULL);
ASSERT (Instance->IaCb.Ia != NULL);
ASSERT (Instance->Service != NULL);
Service = Instance->Service;
ClientId = Service->ClientId;
ASSERT (ClientId != NULL);
//
// Calculate the added length of customized option list.
//
UserLen = 0;
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
}
//
// Create the Dhcp6 packet and initialize common fields.
//
Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Packet->Size = DHCP6_BASE_PACKET_SIZE + UserLen;
Packet->Length = sizeof (EFI_DHCP6_HEADER);
Packet->Dhcp6.Header.MessageType = Dhcp6MsgConfirm;
Packet->Dhcp6.Header.TransactionId = Service->Xid++;
//
// Assembly Dhcp6 options for solicit message.
//
Cursor = Packet->Dhcp6.Option;
Length = HTONS (ClientId->Length);
Cursor = Dhcp6AppendOption (
Cursor,
HTONS (Dhcp6OptClientId),
Length,
ClientId->Duid
);
Cursor = Dhcp6AppendETOption (
Cursor,
Instance,
&Elapsed
);
Cursor = Dhcp6AppendIaOption (
Cursor,
Instance->IaCb.Ia,
Instance->IaCb.T1,
Instance->IaCb.T2,
Packet->Dhcp6.Header.MessageType
);
//
// Append user-defined when configurate Dhcp6 service.
//
for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
UserOpt = Instance->Config->OptionList[Index];
Cursor = Dhcp6AppendOption (
Cursor,
UserOpt->OpCode,
UserOpt->OpLen,
UserOpt->Data
);
}
//
// Determine the size/length of packet.
//
Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
ASSERT (Packet->Size > Packet->Length + 8);
//
// Callback to user with the packet to be sent and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SendConfirm, &Packet);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Send confirm packet with the state transition from Dhcp6Bound to
// Dhcp6Confirming.
//
Instance->IaCb.Ia->State = Dhcp6Confirming;
//
// Clear initial time for current transaction.
//
Instance->StartTime = 0;
Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
if (EFI_ERROR (Status)) {
FreePool (Packet);
return Status;
}
//
// Enqueue the sent packet for the retransmission in case reply timeout.
//
return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);
}
/**
Handle with the Dhcp6 reply message.
@param[in] Instance The pointer to Dhcp6 instance.
@param[in] Packet The pointer to the Dhcp6 reply message.
@retval EFI_SUCCESS Processed the reply message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to process the reply message.
**/
EFI_STATUS
Dhcp6HandleReplyMsg (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
UINT8 *Option;
UINT16 StsCode;
ASSERT (Instance->Config != NULL);
ASSERT (Instance->IaCb.Ia != NULL);
ASSERT (Packet != NULL);
Status = EFI_SUCCESS;
if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply) {
return EFI_DEVICE_ERROR;
}
//
// If the client subsequently receives a valid reply message that includes a
// rapid commit option since send a solicit with rapid commit option before,
// preocess the reply message and discard any reply messages received in
// response to the request message.
// See details in the section-17.1.4 of rfc-3315.
//
Option = Dhcp6SeekOption (
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptRapidCommit
);
if ((Option != NULL && !Instance->Config->RapidCommit) || (Option == NULL && Instance->Config->RapidCommit)) {
return EFI_DEVICE_ERROR;
}
//
// As to a valid reply packet in response to a request/renew/rebind packet,
// ignore the packet if not contains the Ia option
//
if (Instance->IaCb.Ia->State == Dhcp6Requesting ||
Instance->IaCb.Ia->State == Dhcp6Renewing ||
Instance->IaCb.Ia->State == Dhcp6Rebinding
) {
Option = Dhcp6SeekIaOption (
Packet->Dhcp6.Option,
Packet->Length,
&Instance->Config->IaDescriptor
);
if (Option == NULL) {
return EFI_SUCCESS;
}
}
//
// Callback to user with the received packet and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6RcvdReply, &Packet);
if (EFI_ERROR (Status)) {
return Status;
}
//
// When receive a valid reply packet in response to a decline/release packet,
// the client considers the decline/release event completed regardless of the
// status code.
//
if (Instance->IaCb.Ia->State == Dhcp6Declining || Instance->IaCb.Ia->State == Dhcp6Releasing) {
if (Instance->IaCb.Ia->IaAddressCount != 0) {
Instance->IaCb.Ia->State = Dhcp6Bound;
} else {
ASSERT (Instance->IaCb.Ia->ReplyPacket);
FreePool (Instance->IaCb.Ia->ReplyPacket);
Instance->IaCb.Ia->ReplyPacket = NULL;
Instance->IaCb.Ia->State = Dhcp6Init;
}
//
// For sync, set the success flag out of polling in decline/release.
//
Instance->UdpSts = EFI_SUCCESS;
//
// For async, signal the Ia event to inform Ia infomation update.
//
if (Instance->Config->IaInfoEvent != NULL) {
gBS->SignalEvent (Instance->Config->IaInfoEvent);
}
//
// Reset start time for next exchange.
//
Instance->StartTime = 0;
Status = EFI_SUCCESS;
goto ON_EXIT;
}
//
// Upon the receipt of a valid reply packet in response to a solicit, request,
// confirm, renew and rebind, the behavior depends on the status code option.
// See the details in the section-18.1.8 of rfc-3315.
//
Option = NULL;
Status = Dhcp6SeekStsOption (
Instance,
Packet,
&Option
);
if (!EFI_ERROR (Status)) {
//
// No status code or no error status code means succeed to reply.
//
Status = Dhcp6UpdateIaInfo (Instance, Packet);
if (!EFI_ERROR (Status)) {
//
// Reset start time for next exchange.
//
Instance->StartTime = 0;
//
// Set bound state and store the reply packet.
//
if (Instance->IaCb.Ia->ReplyPacket != NULL) {
FreePool (Instance->IaCb.Ia->ReplyPacket);
}
Instance->IaCb.Ia->ReplyPacket = AllocateZeroPool (Packet->Size);
if (Instance->IaCb.Ia->ReplyPacket == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
CopyMem (Instance->IaCb.Ia->ReplyPacket, Packet, Packet->Size);
Instance->IaCb.Ia->State = Dhcp6Bound;
//
// For sync, set the success flag out of polling in start/renewrebind.
//
Instance->UdpSts = EFI_SUCCESS;
//
// Maybe this is a new round DHCP process due to some reason, such as NotOnLink
// ReplyMsg for ConfirmMsg should triger new round to acquire new address. In that
// case, clear old address.ValidLifetime and append to new address. Therefore, DHCP
// consumers can be notified to flush old address.
//
Dhcp6AppendCacheIa (Instance);
//
// For async, signal the Ia event to inform Ia infomation update.
//
if (Instance->Config->IaInfoEvent != NULL) {
gBS->SignalEvent (Instance->Config->IaInfoEvent);
}
} else if (Status == EFI_NOT_FOUND) {
//
// Refer to RFC3315 Chapter 18.1.8, for each IA in the original Renew or Rebind message,
// the client sends a Renew or Rebind if the IA is not in the Reply message.
// Return EFI_SUCCESS so we can continue to restart the Renew/Rebind process.
//
return EFI_SUCCESS;
}
goto ON_EXIT;
} else if (Option != NULL) {
//
// Any error status code option is found.
//
StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 4)));
switch (StsCode) {
case Dhcp6StsUnspecFail:
//
// It indicates the server is unable to process the message due to an
// unspecified failure condition, so just retry if possible.
//
break;
case Dhcp6StsUseMulticast:
//
// It indicates the server receives a message via unicast from a client
// to which the server has not sent a unicast option, so retry it by
// multi-cast address.
//
if (Instance->Unicast != NULL) {
FreePool (Instance->Unicast);
Instance->Unicast = NULL;
}
break;
case Dhcp6StsNotOnLink:
if (Instance->IaCb.Ia->State == Dhcp6Confirming) {
//
// Before initiate new round DHCP, cache the current IA.
//
Status = Dhcp6CacheIa (Instance);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Restart S.A.R.R process to acquire new address.
//
Status = Dhcp6InitSolicitMsg (Instance);
if (EFI_ERROR (Status)) {
return Status;
}
}
break;
case Dhcp6StsNoBinding:
if (Instance->IaCb.Ia->State == Dhcp6Renewing || Instance->IaCb.Ia->State == Dhcp6Rebinding) {
//
// Refer to RFC3315 Chapter 18.1.8, for each IA in the original Renew or Rebind message, the client
// sends a Request message if the IA contained a Status Code option with the NoBinding status.
//
Status = Dhcp6SendRequestMsg(Instance);
if (EFI_ERROR (Status)) {
return Status;
}
}
break;
default:
//
// The other status code, just restart solicitation.
//
break;
}
}
return EFI_SUCCESS;
ON_EXIT:
if (!EFI_ERROR(Status)) {
Status = Dhcp6DequeueRetry (
Instance,
Packet->Dhcp6.Header.TransactionId,
FALSE
);
}
return Status;
}
/**
Select the appointed Dhcp6 advertisement message.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] AdSelect The pointer to the selected Dhcp6 advertisement message.
@retval EFI_SUCCESS Selected the right advertisement message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval Others Failed to select the advertise message.
**/
EFI_STATUS
Dhcp6SelectAdvertiseMsg (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *AdSelect
)
{
EFI_STATUS Status;
UINT8 *Option;
ASSERT (AdSelect != NULL);
//
// Callback to user with the selected advertisement packet, and the user
// might overwrite it.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6SelectAdvertise, &AdSelect);
if (EFI_ERROR (Status)) {
return Status;
}
Instance->AdSelect = AdSelect;
//
// Dequeue the sent packet for the retransmission since advertisement selected.
//
Status = Dhcp6DequeueRetry (
Instance,
AdSelect->Dhcp6.Header.TransactionId,
FALSE
);
if (EFI_ERROR(Status)) {
return Status;
}
//
// Check whether there is server unicast option in the selected advertise
// packet, and update it.
//
Option = Dhcp6SeekOption(
AdSelect->Dhcp6.Option,
AdSelect->Length - 4,
Dhcp6OptServerUnicast
);
if (Option != NULL) {
Instance->Unicast = AllocateZeroPool (sizeof(EFI_IPv6_ADDRESS));
if (Instance->Unicast == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Instance->Unicast, Option + 4, sizeof(EFI_IPv6_ADDRESS));
}
//
// Update the information of the Ia by the selected advertisement message.
//
Status = Dhcp6UpdateIaInfo (Instance, AdSelect);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Send the request message to continue the S.A.R.R. process.
//
return Dhcp6SendRequestMsg (Instance);
}
/**
Handle with the Dhcp6 advertisement message.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to the Dhcp6 advertisement message.
@retval EFI_SUCCESS Processed the advertisement message successfully.
@retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated.
@retval EFI_DEVICE_ERROR An unexpected error.
@retval Others Failed to process the advertise message.
**/
EFI_STATUS
Dhcp6HandleAdvertiseMsg (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
UINT8 *Option;
BOOLEAN Timeout;
ASSERT(Instance->Config);
ASSERT(Instance->IaCb.Ia);
Timeout = FALSE;
//
// If the client does receives a valid reply message that includes a rapid
// commit option since a solicit with rapid commit optioin sent before, select
// this reply message. Or else, process the advertise messages as normal.
// See details in the section-17.1.4 of rfc-3315.
//
Option = Dhcp6SeekOption(
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptRapidCommit
);
if (Option != NULL && Instance->Config->RapidCommit && Packet->Dhcp6.Header.MessageType == Dhcp6MsgReply) {
return Dhcp6HandleReplyMsg (Instance, Packet);
}
if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgAdvertise) {
return EFI_DEVICE_ERROR;
}
//
// Client must ignore any advertise message that includes a status code option
// containing the value noaddrsavail, with the exception that the client may
// display the associated status message to the user.
// See the details in the section-17.1.3 of rfc-3315.
//
Status = Dhcp6SeekStsOption (
Instance,
Packet,
&Option
);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
//
// Callback to user with the received packet and check the user's feedback.
//
Status = Dhcp6CallbackUser (Instance, Dhcp6RcvdAdvertise, &Packet);
if (!EFI_ERROR (Status)) {
//
// Success means user choose the current advertisement packet.
//
if (Instance->AdSelect != NULL) {
FreePool (Instance->AdSelect);
}
//
// Store the selected advertisement packet and set a flag.
//
Instance->AdSelect = AllocateZeroPool (Packet->Size);
if (Instance->AdSelect == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Instance->AdSelect, Packet, Packet->Size);
Instance->AdPref = 0xff;
} else if (Status == EFI_NOT_READY) {
//
// Not_ready means user wants to continue to receive more advertise packets.
//
if (Instance->AdPref == 0xff && Instance->AdSelect == NULL) {
//
// It's a tricky point. The timer routine set adpref as 0xff if the first
// rt timeout and no advertisement received, which means any advertisement
// received will be selected after the first rt.
//
Timeout = TRUE;
}
//
// Check whether the current packet has a 255 preference option or not.
// Take non-preference option as 0 value.
//
Option = Dhcp6SeekOption(
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptPreference
);
if (Instance->AdSelect == NULL || (Option != NULL && *(Option + 4) > Instance->AdPref)) {
//
// No advertisements received before or preference is more than other
// advertisements received before. Then store the new packet and the
// preference value.
//
if (Instance->AdSelect != NULL) {
FreePool (Instance->AdSelect);
}
Instance->AdSelect = AllocateZeroPool (Packet->Size);
if (Instance->AdSelect == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Instance->AdSelect, Packet, Packet->Size);
if (Option != NULL) {
Instance->AdPref = *(Option + 4);
}
} else {
//
// Non-preference and other advertisements received before or current
// preference is less than other advertisements received before.
// Leave the packet alone.
}
} else {
//
// Other error status means termination.
//
return Status;
}
//
// Client must collect advertise messages as more as possible until the first
// RT has elapsed, or get a highest preference 255 advertise.
// See details in the section-17.1.2 of rfc-3315.
//
if (Instance->AdPref == 0xff || Timeout) {
Status = Dhcp6SelectAdvertiseMsg (Instance, Instance->AdSelect);
}
return Status;
}
/**
The Dhcp6 stateful exchange process routine.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to the received Dhcp6 message.
**/
VOID
Dhcp6HandleStateful (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
EFI_DHCP6_DUID *ClientId;
DHCP6_SERVICE *Service;
UINT8 *Option;
Service = Instance->Service;
ClientId = Service->ClientId;
Status = EFI_SUCCESS;
if (Instance->Config == NULL) {
goto ON_CONTINUE;
}
ASSERT (ClientId);
ASSERT (Instance->Config);
ASSERT (Instance->IaCb.Ia);
//
// Discard the packet if not advertisement or reply packet.
//
if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgAdvertise && Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply) {
goto ON_CONTINUE;
}
//
// Check whether include client Id or not.
//
Option = Dhcp6SeekOption(
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptClientId
);
if (Option == NULL || CompareMem (Option + 4, ClientId->Duid, ClientId->Length) != 0) {
goto ON_CONTINUE;
}
//
// Check whether include server Id or not.
//
Option = Dhcp6SeekOption(
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
goto ON_CONTINUE;
}
switch (Instance->IaCb.Ia->State) {
case Dhcp6Selecting:
//
// Handle the advertisement message when in the Dhcp6Selecting state.
// Do not need check return status, if failed, just continue to the next.
//
Dhcp6HandleAdvertiseMsg (Instance, Packet);
break;
case Dhcp6Requesting:
case Dhcp6Confirming:
case Dhcp6Renewing:
case Dhcp6Rebinding:
case Dhcp6Releasing:
case Dhcp6Declining:
//
// Handle the reply message when in the Dhcp6Requesting, Dhcp6Renewing
// Dhcp6Rebinding, Dhcp6Releasing and Dhcp6Declining state.
// If failed here, it should reset the current session.
//
Status = Dhcp6HandleReplyMsg (Instance, Packet);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
break;
default:
//
// Other state has not supported yet.
//
break;
}
ON_CONTINUE:
//
// Continue to receive the following Dhcp6 message.
//
Status = UdpIoRecvDatagram (
Service->UdpIo,
Dhcp6ReceivePacket,
Service,
0
);
ON_EXIT:
if (EFI_ERROR (Status)) {
Dhcp6CleanupSession (Instance, Status);
}
}
/**
The Dhcp6 stateless exchange process routine.
@param[in] Instance The pointer to the Dhcp6 instance.
@param[in] Packet The pointer to the received Dhcp6 message.
**/
VOID
Dhcp6HandleStateless (
IN DHCP6_INSTANCE *Instance,
IN EFI_DHCP6_PACKET *Packet
)
{
EFI_STATUS Status;
DHCP6_SERVICE *Service;
DHCP6_INF_CB *InfCb;
UINT8 *Option;
BOOLEAN IsMatched;
Service = Instance->Service;
Status = EFI_SUCCESS;
IsMatched = FALSE;
InfCb = NULL;
if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply) {
goto ON_EXIT;
}
//
// Check whether it's a desired Info-request message by Xid.
//
while (!IsListEmpty (&Instance->InfList)) {
InfCb = NET_LIST_HEAD (&Instance->InfList, DHCP6_INF_CB, Link);
if (InfCb->Xid == Packet->Dhcp6.Header.TransactionId) {
IsMatched = TRUE;
break;
}
}
if (!IsMatched) {
goto ON_EXIT;
}
//
// Check whether include server Id or not.
//
Option = Dhcp6SeekOption (
Packet->Dhcp6.Option,
Packet->Length - 4,
Dhcp6OptServerId
);
if (Option == NULL) {
goto ON_EXIT;
}
//
// Callback to user with the received packet and check the user's feedback.
//
Status = InfCb->ReplyCallback (
&Instance->Dhcp6,
InfCb->CallbackContext,
Packet
);
if (Status == EFI_NOT_READY) {
//
// Success or aborted will both stop this info-request exchange process,
// but not ready means user wants to continue to receive reply.
//
goto ON_EXIT;
}
//
// Dequeue the sent packet from the txlist if the xid matched, and ignore
// if no xid matched.
//
Dhcp6DequeueRetry (
Instance,
Packet->Dhcp6.Header.TransactionId,
FALSE
);
//
// For sync, set the status out of polling for info-request.
//
Instance->UdpSts = Status;
ON_EXIT:
Status = UdpIoRecvDatagram (
Service->UdpIo,
Dhcp6ReceivePacket,
Service,
0
);
if (EFI_ERROR (Status)) {
Dhcp6CleanupRetry (Instance, DHCP6_PACKET_STATELESS);
}
}
/**
The receive callback function for Dhcp6 exchange process.
@param[in] Udp6Wrap The pointer to the received net buffer.
@param[in] EndPoint The pointer to the udp end point.
@param[in] IoStatus The return status from udp io.
@param[in] Context The opaque parameter to the function.
**/
VOID
EFIAPI
Dhcp6ReceivePacket (
IN NET_BUF *Udp6Wrap,
IN UDP_END_POINT *EndPoint,
IN EFI_STATUS IoStatus,
IN VOID *Context
)
{
EFI_DHCP6_HEADER *Head;
EFI_DHCP6_PACKET *Packet;
DHCP6_SERVICE *Service;
DHCP6_INSTANCE *Instance;
DHCP6_TX_CB *TxCb;
UINT32 Size;
BOOLEAN IsDispatched;
BOOLEAN IsStateless;
LIST_ENTRY *Entry1;
LIST_ENTRY *Next1;
LIST_ENTRY *Entry2;
LIST_ENTRY *Next2;
EFI_STATUS Status;
ASSERT (Udp6Wrap != NULL);
ASSERT (Context != NULL);
Service = (DHCP6_SERVICE *) Context;
Instance = NULL;
Packet = NULL;
IsDispatched = FALSE;
IsStateless = FALSE;
if (EFI_ERROR (IoStatus)) {
return ;
}
if (Udp6Wrap->TotalSize < sizeof (EFI_DHCP6_HEADER)) {
goto ON_CONTINUE;
}
//
// Copy the net buffer received from upd6 to a Dhcp6 packet.
//
Size = sizeof (EFI_DHCP6_PACKET) + Udp6Wrap->TotalSize;
Packet = (EFI_DHCP6_PACKET *) AllocateZeroPool (Size);
if (Packet == NULL) {
goto ON_CONTINUE;
}
Packet->Size = Size;
Head = &Packet->Dhcp6.Header;
Packet->Length = NetbufCopy (Udp6Wrap, 0, Udp6Wrap->TotalSize, (UINT8 *) Head);
if (Packet->Length == 0) {
goto ON_CONTINUE;
}
//
// Dispatch packet to right instance by transaction id.
//
NET_LIST_FOR_EACH_SAFE (Entry1, Next1, &Service->Child) {
Instance = NET_LIST_USER_STRUCT (Entry1, DHCP6_INSTANCE, Link);
NET_LIST_FOR_EACH_SAFE (Entry2, Next2, &Instance->TxList) {
TxCb = NET_LIST_USER_STRUCT (Entry2, DHCP6_TX_CB, Link);
if (Packet->Dhcp6.Header.TransactionId == TxCb->Xid) {
//
// Find the corresponding packet in tx list, and check it whether belongs
// to stateful exchange process.
//
if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
IsStateless = TRUE;
}
IsDispatched = TRUE;
break;
}
}
if (IsDispatched) {
break;
}
}
//
// Skip this packet if not dispatched to any instance.
//
if (!IsDispatched) {
goto ON_CONTINUE;
}
//
// Dispatch the received packet ot the right instance.
//
if (IsStateless) {
Dhcp6HandleStateless (Instance, Packet);
} else {
Dhcp6HandleStateful (Instance, Packet);
}
ON_CONTINUE:
if (!IsDispatched) {
Status = UdpIoRecvDatagram (
Service->UdpIo,
Dhcp6ReceivePacket,
Service,
0
);
if (EFI_ERROR (Status)) {
NET_LIST_FOR_EACH_SAFE (Entry1, Next1, &Service->Child) {
Instance = NET_LIST_USER_STRUCT (Entry1, DHCP6_INSTANCE, Link);
Dhcp6CleanupRetry (Instance, DHCP6_PACKET_ALL);
}
}
}
NetbufFree (Udp6Wrap);
if (Packet != NULL) {
FreePool (Packet);
}
}
/**
Detect Link movement for specified network device.
This routine will try to invoke Snp->GetStatus() to get the media status.
If media present status switches from unpresent to present, a link movement
is detected. Note that the underlying UNDI driver may not support reporting
media status from GET_STATUS command. If that, fail to detect link movement.
@param[in] Instance The pointer to DHCP6_INSTANCE.
@retval TRUE A link movement is detected.
@retval FALSE A link movement is not detected.
**/
BOOLEAN
Dhcp6LinkMovDetect (
IN DHCP6_INSTANCE *Instance
)
{
UINT32 InterruptStatus;
BOOLEAN MediaPresent;
EFI_STATUS Status;
EFI_SIMPLE_NETWORK_PROTOCOL *Snp;
ASSERT (Instance != NULL);
Snp = Instance->Service->Snp;
MediaPresent = Instance->MediaPresent;
//
// Check whether SNP support media detection
//
if (!Snp->Mode->MediaPresentSupported) {
return FALSE;
}
//
// Invoke Snp->GetStatus() to refresh MediaPresent field in SNP mode data
//
Status = Snp->GetStatus (Snp, &InterruptStatus, NULL);
if (EFI_ERROR (Status)) {
return FALSE;
}
Instance->MediaPresent = Snp->Mode->MediaPresent;
//
// Media transimit Unpresent to Present means new link movement is detected.
//
if (!MediaPresent && Instance->MediaPresent) {
return TRUE;
}
return FALSE;
}
/**
The timer routine of the Dhcp6 instance for each second.
@param[in] Event The timer event.
@param[in] Context The opaque parameter to the function.
**/
VOID
EFIAPI
Dhcp6OnTimerTick (
IN EFI_EVENT Event,
IN VOID *Context
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
DHCP6_INSTANCE *Instance;
DHCP6_TX_CB *TxCb;
DHCP6_IA_CB *IaCb;
UINT32 LossTime;
EFI_STATUS Status;
ASSERT (Context != NULL);
Instance = (DHCP6_INSTANCE *) Context;
//
// 1. Loop the tx list, count live time of every tx packet to check whether
// need re-transmit or not.
//
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
TxCb->TickTime++;
if (TxCb->TickTime > TxCb->RetryExp) {
//
// Handle the first rt in the transmission of solicit specially.
//
if ((TxCb->RetryCnt == 0 || TxCb->SolicitRetry) && TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgSolicit) {
if (Instance->AdSelect == NULL) {
//
// Set adpref as 0xff here to indicate select any advertisement
// afterwards.
//
Instance->AdPref = 0xff;
} else {
//
// Select the advertisement received before.
//
Status = Dhcp6SelectAdvertiseMsg (Instance, Instance->AdSelect);
if (Status == EFI_ABORTED) {
goto ON_CLOSE;
} else if (EFI_ERROR (Status)) {
TxCb->RetryCnt++;
}
return;
}
}
//
// Increase the retry count for the packet and add up the total loss time.
//
TxCb->RetryCnt++;
TxCb->RetryLos += TxCb->RetryExp;
//
// Check whether overflow the max retry count limit for this packet
//
if (TxCb->RetryCtl.Mrc != 0 && TxCb->RetryCtl.Mrc < TxCb->RetryCnt) {
Status = EFI_NO_RESPONSE;
goto ON_CLOSE;
}
//
// Check whether overflow the max retry duration for this packet
//
if (TxCb->RetryCtl.Mrd != 0 && TxCb->RetryCtl.Mrd <= TxCb->RetryLos) {
Status = EFI_NO_RESPONSE;
goto ON_CLOSE;
}
//
// Re-calculate retry expire timeout for the next time.
//
// Firstly, Check the new calculated time whether overflow the max retry
// expire time.
//
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryExp,
FALSE,
TRUE
);
if (TxCb->RetryCtl.Mrt != 0 && TxCb->RetryCtl.Mrt < TxCb->RetryExp) {
TxCb->RetryExp = Dhcp6CalculateExpireTime (
TxCb->RetryCtl.Mrt,
TRUE,
TRUE
);
}
//
// Secondly, Check the new calculated time whether overflow the max retry
// duration time.
//
LossTime = TxCb->RetryLos + TxCb->RetryExp;
if (TxCb->RetryCtl.Mrd != 0 && TxCb->RetryCtl.Mrd < LossTime) {
TxCb->RetryExp = TxCb->RetryCtl.Mrd - TxCb->RetryLos;
}
//
// Reset the tick time for the next retransmission
//
TxCb->TickTime = 0;
//
// Retransmit the last sent packet again.
//
Dhcp6TransmitPacket (Instance, TxCb->TxPacket, TxCb->Elapsed);
TxCb->SolicitRetry = FALSE;
if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgSolicit) {
TxCb->SolicitRetry = TRUE;
}
}
}
//
// 2. Check the configured Ia, count lease time of every valid Ia to check
// whether need to renew or rebind this Ia.
//
IaCb = &Instance->IaCb;
if (Instance->Config == NULL || IaCb->Ia == NULL) {
return;
}
if (IaCb->Ia->State == Dhcp6Bound || IaCb->Ia->State == Dhcp6Renewing || IaCb->Ia->State == Dhcp6Rebinding) {
IaCb->LeaseTime++;
if (IaCb->LeaseTime > IaCb->T2 && IaCb->Ia->State == Dhcp6Bound) {
//
// Exceed t2, send rebind packet to extend the Ia lease.
//
Dhcp6SendRenewRebindMsg (Instance, TRUE);
} else if (IaCb->LeaseTime > IaCb->T1 && IaCb->Ia->State == Dhcp6Bound) {
//
// Exceed t1, send renew packet to extend the Ia lease.
//
Dhcp6SendRenewRebindMsg (Instance, FALSE);
}
}
//
// 3. In any situation when a client may have moved to a new link, the
// client MUST initiate a Confirm/Reply message exchange.
//
if (Dhcp6LinkMovDetect (Instance) && (IaCb->Ia->State == Dhcp6Bound)) {
Dhcp6SendConfirmMsg (Instance);
}
return;
ON_CLOSE:
if (Dhcp6IsValidTxCb (Instance, TxCb) &&
TxCb->TxPacket != NULL &&
(TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest ||
TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgRenew ||
TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgConfirm)
) {
//
// The failure of renew/Confirm will still switch to the bound state.
//
if ((TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgRenew) ||
(TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgConfirm)) {
ASSERT (Instance->IaCb.Ia);
Instance->IaCb.Ia->State = Dhcp6Bound;
}
//
// The failure of info-request will return no response.
//
if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
Instance->UdpSts = EFI_NO_RESPONSE;
}
Dhcp6DequeueRetry (
Instance,
TxCb->Xid,
TRUE
);
} else {
//
// The failure of the others will terminate current state machine if timeout.
//
Dhcp6CleanupSession (Instance, Status);
}
}