CloverBootloader/NetworkPkg/Ip6Dxe/Ip6Nd.c

3174 lines
100 KiB
C

/** @file
Implementation of Neighbor Discovery support routines.
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Ip6Impl.h"
EFI_MAC_ADDRESS mZeroMacAddress;
/**
Update the ReachableTime in IP6 service binding instance data, in milliseconds.
@param[in, out] IpSb Points to the IP6_SERVICE.
**/
VOID
Ip6UpdateReachableTime (
IN OUT IP6_SERVICE *IpSb
)
{
UINT32 Random;
Random = (NetRandomInitSeed () / 4294967295UL) * IP6_RANDOM_FACTOR_SCALE;
Random = Random + IP6_MIN_RANDOM_FACTOR_SCALED;
IpSb->ReachableTime = (IpSb->BaseReachableTime * Random) / IP6_RANDOM_FACTOR_SCALE;
}
/**
Build a array of EFI_IP6_NEIGHBOR_CACHE to be returned to the caller. The number
of EFI_IP6_NEIGHBOR_CACHE is also returned.
@param[in] IpInstance The pointer to IP6_PROTOCOL instance.
@param[out] NeighborCount The number of returned neighbor cache entries.
@param[out] NeighborCache The pointer to the array of EFI_IP6_NEIGHBOR_CACHE.
@retval EFI_SUCCESS The EFI_IP6_NEIGHBOR_CACHE successfully built.
@retval EFI_OUT_OF_RESOURCES Failed to allocate the memory for the route table.
**/
EFI_STATUS
Ip6BuildEfiNeighborCache (
IN IP6_PROTOCOL *IpInstance,
OUT UINT32 *NeighborCount,
OUT EFI_IP6_NEIGHBOR_CACHE **NeighborCache
)
{
IP6_NEIGHBOR_ENTRY *Neighbor;
LIST_ENTRY *Entry;
IP6_SERVICE *IpSb;
UINT32 Count;
EFI_IP6_NEIGHBOR_CACHE *EfiNeighborCache;
EFI_IP6_NEIGHBOR_CACHE *NeighborCacheTmp;
NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
ASSERT (NeighborCount != NULL && NeighborCache != NULL);
IpSb = IpInstance->Service;
Count = 0;
NET_LIST_FOR_EACH (Entry, &IpSb->NeighborTable) {
Count++;
}
if (Count == 0) {
return EFI_SUCCESS;
}
NeighborCacheTmp = AllocatePool (Count * sizeof (EFI_IP6_NEIGHBOR_CACHE));
if (NeighborCacheTmp == NULL) {
return EFI_OUT_OF_RESOURCES;
}
*NeighborCount = Count;
Count = 0;
NET_LIST_FOR_EACH (Entry, &IpSb->NeighborTable) {
Neighbor = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);
EfiNeighborCache = NeighborCacheTmp + Count;
EfiNeighborCache->State = Neighbor->State;
IP6_COPY_ADDRESS (&EfiNeighborCache->Neighbor, &Neighbor->Neighbor);
IP6_COPY_LINK_ADDRESS (&EfiNeighborCache->LinkAddress, &Neighbor->LinkAddress);
Count++;
}
ASSERT (*NeighborCount == Count);
*NeighborCache = NeighborCacheTmp;
return EFI_SUCCESS;
}
/**
Build a array of EFI_IP6_ADDRESS_INFO to be returned to the caller. The number
of prefix entries is also returned.
@param[in] IpInstance The pointer to IP6_PROTOCOL instance.
@param[out] PrefixCount The number of returned prefix entries.
@param[out] PrefixTable The pointer to the array of PrefixTable.
@retval EFI_SUCCESS The prefix table successfully built.
@retval EFI_OUT_OF_RESOURCES Failed to allocate the memory for the prefix table.
**/
EFI_STATUS
Ip6BuildPrefixTable (
IN IP6_PROTOCOL *IpInstance,
OUT UINT32 *PrefixCount,
OUT EFI_IP6_ADDRESS_INFO **PrefixTable
)
{
LIST_ENTRY *Entry;
IP6_SERVICE *IpSb;
UINT32 Count;
IP6_PREFIX_LIST_ENTRY *PrefixList;
EFI_IP6_ADDRESS_INFO *EfiPrefix;
EFI_IP6_ADDRESS_INFO *PrefixTableTmp;
NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
ASSERT (PrefixCount != NULL && PrefixTable != NULL);
IpSb = IpInstance->Service;
Count = 0;
NET_LIST_FOR_EACH (Entry, &IpSb->OnlinkPrefix) {
Count++;
}
if (Count == 0) {
return EFI_SUCCESS;
}
PrefixTableTmp = AllocatePool (Count * sizeof (EFI_IP6_ADDRESS_INFO));
if (PrefixTableTmp == NULL) {
return EFI_OUT_OF_RESOURCES;
}
*PrefixCount = Count;
Count = 0;
NET_LIST_FOR_EACH (Entry, &IpSb->OnlinkPrefix) {
PrefixList = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
EfiPrefix = PrefixTableTmp + Count;
IP6_COPY_ADDRESS (&EfiPrefix->Address, &PrefixList->Prefix);
EfiPrefix->PrefixLength = PrefixList->PrefixLength;
Count++;
}
ASSERT (*PrefixCount == Count);
*PrefixTable = PrefixTableTmp;
return EFI_SUCCESS;
}
/**
Allocate and initialize a IP6 prefix list entry.
@param[in] IpSb The pointer to IP6_SERVICE instance.
@param[in] OnLinkOrAuto If TRUE, the entry is created for the on link prefix list.
Otherwise, it is created for the autoconfiguration prefix list.
@param[in] ValidLifetime The length of time in seconds that the prefix
is valid for the purpose of on-link determination.
@param[in] PreferredLifetime The length of time in seconds that addresses
generated from the prefix via stateless address
autoconfiguration remain preferred.
@param[in] PrefixLength The prefix length of the Prefix.
@param[in] Prefix The prefix address.
@return NULL if it failed to allocate memory for the prefix node. Otherwise, point
to the created or existing prefix list entry.
**/
IP6_PREFIX_LIST_ENTRY *
Ip6CreatePrefixListEntry (
IN IP6_SERVICE *IpSb,
IN BOOLEAN OnLinkOrAuto,
IN UINT32 ValidLifetime,
IN UINT32 PreferredLifetime,
IN UINT8 PrefixLength,
IN EFI_IPv6_ADDRESS *Prefix
)
{
IP6_PREFIX_LIST_ENTRY *PrefixEntry;
IP6_ROUTE_ENTRY *RtEntry;
LIST_ENTRY *ListHead;
LIST_ENTRY *Entry;
IP6_PREFIX_LIST_ENTRY *TmpPrefixEntry;
if ((Prefix == NULL) || (PreferredLifetime > ValidLifetime) || (PrefixLength > IP6_PREFIX_MAX)) {
return NULL;
}
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
PrefixEntry = Ip6FindPrefixListEntry (
IpSb,
OnLinkOrAuto,
PrefixLength,
Prefix
);
if (PrefixEntry != NULL) {
PrefixEntry->RefCnt++;
return PrefixEntry;
}
PrefixEntry = AllocatePool (sizeof (IP6_PREFIX_LIST_ENTRY));
if (PrefixEntry == NULL) {
return NULL;
}
PrefixEntry->RefCnt = 1;
PrefixEntry->ValidLifetime = ValidLifetime;
PrefixEntry->PreferredLifetime = PreferredLifetime;
PrefixEntry->PrefixLength = PrefixLength;
IP6_COPY_ADDRESS (&PrefixEntry->Prefix, Prefix);
ListHead = OnLinkOrAuto ? &IpSb->OnlinkPrefix : &IpSb->AutonomousPrefix;
//
// Create a direct route entry for on-link prefix and insert to route area.
//
if (OnLinkOrAuto) {
RtEntry = Ip6CreateRouteEntry (Prefix, PrefixLength, NULL);
if (RtEntry == NULL) {
FreePool (PrefixEntry);
return NULL;
}
RtEntry->Flag = IP6_DIRECT_ROUTE;
InsertHeadList (&IpSb->RouteTable->RouteArea[PrefixLength], &RtEntry->Link);
IpSb->RouteTable->TotalNum++;
}
//
// Insert the prefix entry in the order that a prefix with longer prefix length
// is put ahead in the list.
//
NET_LIST_FOR_EACH (Entry, ListHead) {
TmpPrefixEntry = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
if (TmpPrefixEntry->PrefixLength < PrefixEntry->PrefixLength) {
break;
}
}
NetListInsertBefore (Entry, &PrefixEntry->Link);
return PrefixEntry;
}
/**
Destroy a IP6 prefix list entry.
@param[in] IpSb The pointer to IP6_SERVICE instance.
@param[in] PrefixEntry The to be destroyed prefix list entry.
@param[in] OnLinkOrAuto If TRUE, the entry is removed from on link prefix list.
Otherwise remove from autoconfiguration prefix list.
@param[in] ImmediateDelete If TRUE, remove the entry directly.
Otherwise, check the reference count to see whether
it should be removed.
**/
VOID
Ip6DestroyPrefixListEntry (
IN IP6_SERVICE *IpSb,
IN IP6_PREFIX_LIST_ENTRY *PrefixEntry,
IN BOOLEAN OnLinkOrAuto,
IN BOOLEAN ImmediateDelete
)
{
LIST_ENTRY *Entry;
IP6_INTERFACE *IpIf;
EFI_STATUS Status;
if ((!ImmediateDelete) && (PrefixEntry->RefCnt > 0) && ((--PrefixEntry->RefCnt) > 0)) {
return;
}
if (OnLinkOrAuto) {
//
// Remove the direct route for onlink prefix from route table.
//
do {
Status = Ip6DelRoute (
IpSb->RouteTable,
&PrefixEntry->Prefix,
PrefixEntry->PrefixLength,
NULL
);
} while (Status != EFI_NOT_FOUND);
} else {
//
// Remove the corresponding addresses generated from this autonomous prefix.
//
NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
IpIf = NET_LIST_USER_STRUCT_S (Entry, IP6_INTERFACE, Link, IP6_INTERFACE_SIGNATURE);
Ip6RemoveAddr (IpSb, &IpIf->AddressList, &IpIf->AddressCount, &PrefixEntry->Prefix, PrefixEntry->PrefixLength);
}
}
RemoveEntryList (&PrefixEntry->Link);
FreePool (PrefixEntry);
}
/**
Search the list array to find an IP6 prefix list entry.
@param[in] IpSb The pointer to IP6_SERVICE instance.
@param[in] OnLinkOrAuto If TRUE, the search the link prefix list,
Otherwise search the autoconfiguration prefix list.
@param[in] PrefixLength The prefix length of the Prefix
@param[in] Prefix The prefix address.
@return NULL if cannot find the IP6 prefix list entry. Otherwise, return the
pointer to the IP6 prefix list entry.
**/
IP6_PREFIX_LIST_ENTRY *
Ip6FindPrefixListEntry (
IN IP6_SERVICE *IpSb,
IN BOOLEAN OnLinkOrAuto,
IN UINT8 PrefixLength,
IN EFI_IPv6_ADDRESS *Prefix
)
{
IP6_PREFIX_LIST_ENTRY *PrefixList;
LIST_ENTRY *Entry;
LIST_ENTRY *ListHead;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ASSERT (Prefix != NULL);
if (OnLinkOrAuto) {
ListHead = &IpSb->OnlinkPrefix;
} else {
ListHead = &IpSb->AutonomousPrefix;
}
NET_LIST_FOR_EACH (Entry, ListHead) {
PrefixList = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
if (PrefixLength != 255) {
//
// Perform exactly prefix match.
//
if ((PrefixList->PrefixLength == PrefixLength) &&
NetIp6IsNetEqual (&PrefixList->Prefix, Prefix, PrefixLength))
{
return PrefixList;
}
} else {
//
// Perform the longest prefix match. The list is already sorted with
// the longest length prefix put at the head of the list.
//
if (NetIp6IsNetEqual (&PrefixList->Prefix, Prefix, PrefixList->PrefixLength)) {
return PrefixList;
}
}
}
return NULL;
}
/**
Release the resource in the prefix list table, and destroy the list entry and
corresponding addresses or route entries.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] ListHead The list entry head of the prefix list table.
**/
VOID
Ip6CleanPrefixListTable (
IN IP6_SERVICE *IpSb,
IN LIST_ENTRY *ListHead
)
{
IP6_PREFIX_LIST_ENTRY *PrefixList;
BOOLEAN OnLink;
OnLink = (BOOLEAN)(ListHead == &IpSb->OnlinkPrefix);
while (!IsListEmpty (ListHead)) {
PrefixList = NET_LIST_HEAD (ListHead, IP6_PREFIX_LIST_ENTRY, Link);
Ip6DestroyPrefixListEntry (IpSb, PrefixList, OnLink, TRUE);
}
}
/**
Callback function when address resolution is finished. It will cancel
all the queued frames if the address resolution failed, or transmit them
if the request succeeded.
@param[in] Context The context of the callback, a pointer to IP6_NEIGHBOR_ENTRY.
**/
VOID
Ip6OnArpResolved (
IN VOID *Context
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Next;
IP6_NEIGHBOR_ENTRY *ArpQue;
IP6_SERVICE *IpSb;
IP6_LINK_TX_TOKEN *Token;
EFI_STATUS Status;
BOOLEAN Sent;
ArpQue = (IP6_NEIGHBOR_ENTRY *)Context;
if ((ArpQue == NULL) || (ArpQue->Interface == NULL)) {
return;
}
IpSb = ArpQue->Interface->Service;
if ((IpSb == NULL) || (IpSb->Signature != IP6_SERVICE_SIGNATURE)) {
return;
}
//
// ARP resolve failed for some reason. Release all the frame
// and ARP queue itself. Ip6FreeArpQue will call the frame's
// owner back.
//
if (NET_MAC_EQUAL (&ArpQue->LinkAddress, &mZeroMacAddress, IpSb->SnpMode.HwAddressSize)) {
Ip6FreeNeighborEntry (IpSb, ArpQue, FALSE, TRUE, EFI_NO_MAPPING, NULL, NULL);
return;
}
//
// ARP resolve succeeded, Transmit all the frame.
//
Sent = FALSE;
NET_LIST_FOR_EACH_SAFE (Entry, Next, &ArpQue->Frames) {
RemoveEntryList (Entry);
Token = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link);
IP6_COPY_LINK_ADDRESS (&Token->DstMac, &ArpQue->LinkAddress);
//
// Insert the tx token before transmitting it via MNP as the FrameSentDpc
// may be called before Mnp->Transmit returns which will remove this tx
// token from the SentFrames list. Remove it from the list if the returned
// Status of Mnp->Transmit is not EFI_SUCCESS as in this case the
// FrameSentDpc won't be queued.
//
InsertTailList (&ArpQue->Interface->SentFrames, &Token->Link);
Status = IpSb->Mnp->Transmit (IpSb->Mnp, &Token->MnpToken);
if (EFI_ERROR (Status)) {
RemoveEntryList (&Token->Link);
Token->CallBack (Token->Packet, Status, 0, Token->Context);
Ip6FreeLinkTxToken (Token);
continue;
} else {
Sent = TRUE;
}
}
//
// Free the ArpQue only but not the whole neighbor entry.
//
Ip6FreeNeighborEntry (IpSb, ArpQue, FALSE, FALSE, EFI_SUCCESS, NULL, NULL);
if (Sent && (ArpQue->State == EfiNeighborStale)) {
ArpQue->State = EfiNeighborDelay;
ArpQue->Ticks = (UINT32)IP6_GET_TICKS (IP6_DELAY_FIRST_PROBE_TIME);
}
}
/**
Allocate and initialize an IP6 neighbor cache entry.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] CallBack The callback function to be called when
address resolution is finished.
@param[in] Ip6Address Points to the IPv6 address of the neighbor.
@param[in] LinkAddress Points to the MAC address of the neighbor.
Ignored if NULL.
@return NULL if failed to allocate memory for the neighbor cache entry.
Otherwise, point to the created neighbor cache entry.
**/
IP6_NEIGHBOR_ENTRY *
Ip6CreateNeighborEntry (
IN IP6_SERVICE *IpSb,
IN IP6_ARP_CALLBACK CallBack,
IN EFI_IPv6_ADDRESS *Ip6Address,
IN EFI_MAC_ADDRESS *LinkAddress OPTIONAL
)
{
IP6_NEIGHBOR_ENTRY *Entry;
IP6_DEFAULT_ROUTER *DefaultRouter;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ASSERT (Ip6Address != NULL);
Entry = AllocateZeroPool (sizeof (IP6_NEIGHBOR_ENTRY));
if (Entry == NULL) {
return NULL;
}
Entry->RefCnt = 1;
Entry->IsRouter = FALSE;
Entry->ArpFree = FALSE;
Entry->Dynamic = FALSE;
Entry->State = EfiNeighborInComplete;
Entry->Transmit = IP6_MAX_MULTICAST_SOLICIT + 1;
Entry->CallBack = CallBack;
Entry->Interface = NULL;
InitializeListHead (&Entry->Frames);
IP6_COPY_ADDRESS (&Entry->Neighbor, Ip6Address);
if (LinkAddress != NULL) {
IP6_COPY_LINK_ADDRESS (&Entry->LinkAddress, LinkAddress);
} else {
IP6_COPY_LINK_ADDRESS (&Entry->LinkAddress, &mZeroMacAddress);
}
InsertHeadList (&IpSb->NeighborTable, &Entry->Link);
//
// If corresponding default router entry exists, establish the relationship.
//
DefaultRouter = Ip6FindDefaultRouter (IpSb, Ip6Address);
if (DefaultRouter != NULL) {
DefaultRouter->NeighborCache = Entry;
}
return Entry;
}
/**
Search a IP6 neighbor cache entry.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] Ip6Address Points to the IPv6 address of the neighbor.
@return NULL if it failed to find the matching neighbor cache entry.
Otherwise, point to the found neighbor cache entry.
**/
IP6_NEIGHBOR_ENTRY *
Ip6FindNeighborEntry (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *Ip6Address
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Next;
IP6_NEIGHBOR_ENTRY *Neighbor;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ASSERT (Ip6Address != NULL);
NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->NeighborTable) {
Neighbor = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);
if (EFI_IP6_EQUAL (Ip6Address, &Neighbor->Neighbor)) {
RemoveEntryList (Entry);
InsertHeadList (&IpSb->NeighborTable, Entry);
return Neighbor;
}
}
return NULL;
}
/**
Free a IP6 neighbor cache entry and remove all the frames on the address
resolution queue that pass the FrameToCancel. That is, either FrameToCancel
is NULL, or it returns true for the frame.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] NeighborCache The to be free neighbor cache entry.
@param[in] SendIcmpError If TRUE, send out ICMP error.
@param[in] FullFree If TRUE, remove the neighbor cache entry.
Otherwise remove the pending frames.
@param[in] IoStatus The status returned to the cancelled frames'
callback function.
@param[in] FrameToCancel Function to select which frame to cancel.
This is an optional parameter that may be NULL.
@param[in] Context Opaque parameter to the FrameToCancel.
Ignored if FrameToCancel is NULL.
@retval EFI_INVALID_PARAMETER The input parameter is invalid.
@retval EFI_SUCCESS The operation finished successfully.
**/
EFI_STATUS
Ip6FreeNeighborEntry (
IN IP6_SERVICE *IpSb,
IN IP6_NEIGHBOR_ENTRY *NeighborCache,
IN BOOLEAN SendIcmpError,
IN BOOLEAN FullFree,
IN EFI_STATUS IoStatus,
IN IP6_FRAME_TO_CANCEL FrameToCancel OPTIONAL,
IN VOID *Context OPTIONAL
)
{
IP6_LINK_TX_TOKEN *TxToken;
LIST_ENTRY *Entry;
LIST_ENTRY *Next;
IP6_DEFAULT_ROUTER *DefaultRouter;
//
// If FrameToCancel fails, the token will not be released.
// To avoid the memory leak, stop this usage model.
//
if (FullFree && (FrameToCancel != NULL)) {
return EFI_INVALID_PARAMETER;
}
NET_LIST_FOR_EACH_SAFE (Entry, Next, &NeighborCache->Frames) {
TxToken = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link);
if (SendIcmpError && !IP6_IS_MULTICAST (&TxToken->Packet->Ip.Ip6->DestinationAddress)) {
Ip6SendIcmpError (
IpSb,
TxToken->Packet,
NULL,
&TxToken->Packet->Ip.Ip6->SourceAddress,
ICMP_V6_DEST_UNREACHABLE,
ICMP_V6_ADDR_UNREACHABLE,
NULL
);
}
if ((FrameToCancel == NULL) || FrameToCancel (TxToken, Context)) {
RemoveEntryList (Entry);
TxToken->CallBack (TxToken->Packet, IoStatus, 0, TxToken->Context);
Ip6FreeLinkTxToken (TxToken);
}
}
if (NeighborCache->ArpFree && IsListEmpty (&NeighborCache->Frames)) {
RemoveEntryList (&NeighborCache->ArpList);
NeighborCache->ArpFree = FALSE;
}
if (FullFree) {
if (NeighborCache->IsRouter) {
DefaultRouter = Ip6FindDefaultRouter (IpSb, &NeighborCache->Neighbor);
if (DefaultRouter != NULL) {
Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
}
}
RemoveEntryList (&NeighborCache->Link);
FreePool (NeighborCache);
}
return EFI_SUCCESS;
}
/**
Allocate and initialize an IP6 default router entry.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] Ip6Address The IPv6 address of the default router.
@param[in] RouterLifetime The lifetime associated with the default
router, in units of seconds.
@return NULL if it failed to allocate memory for the default router node.
Otherwise, point to the created default router node.
**/
IP6_DEFAULT_ROUTER *
Ip6CreateDefaultRouter (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *Ip6Address,
IN UINT16 RouterLifetime
)
{
IP6_DEFAULT_ROUTER *Entry;
IP6_ROUTE_ENTRY *RtEntry;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ASSERT (Ip6Address != NULL);
Entry = AllocatePool (sizeof (IP6_DEFAULT_ROUTER));
if (Entry == NULL) {
return NULL;
}
Entry->RefCnt = 1;
Entry->Lifetime = RouterLifetime;
Entry->NeighborCache = Ip6FindNeighborEntry (IpSb, Ip6Address);
IP6_COPY_ADDRESS (&Entry->Router, Ip6Address);
//
// Add a default route into route table with both Destination and PrefixLength set to zero.
//
RtEntry = Ip6CreateRouteEntry (NULL, 0, Ip6Address);
if (RtEntry == NULL) {
FreePool (Entry);
return NULL;
}
InsertHeadList (&IpSb->RouteTable->RouteArea[0], &RtEntry->Link);
IpSb->RouteTable->TotalNum++;
InsertTailList (&IpSb->DefaultRouterList, &Entry->Link);
return Entry;
}
/**
Destroy an IP6 default router entry.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] DefaultRouter The to be destroyed IP6_DEFAULT_ROUTER.
**/
VOID
Ip6DestroyDefaultRouter (
IN IP6_SERVICE *IpSb,
IN IP6_DEFAULT_ROUTER *DefaultRouter
)
{
EFI_STATUS Status;
RemoveEntryList (&DefaultRouter->Link);
//
// Update the Destination Cache - all entries using the time-out router as next-hop
// should perform next-hop determination again.
//
do {
Status = Ip6DelRoute (IpSb->RouteTable, NULL, 0, &DefaultRouter->Router);
} while (Status != EFI_NOT_FOUND);
FreePool (DefaultRouter);
}
/**
Clean an IP6 default router list.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
**/
VOID
Ip6CleanDefaultRouterList (
IN IP6_SERVICE *IpSb
)
{
IP6_DEFAULT_ROUTER *DefaultRouter;
while (!IsListEmpty (&IpSb->DefaultRouterList)) {
DefaultRouter = NET_LIST_HEAD (&IpSb->DefaultRouterList, IP6_DEFAULT_ROUTER, Link);
Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
}
}
/**
Search a default router node from an IP6 default router list.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] Ip6Address The IPv6 address of the to be searched default router node.
@return NULL if it failed to find the matching default router node.
Otherwise, point to the found default router node.
**/
IP6_DEFAULT_ROUTER *
Ip6FindDefaultRouter (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *Ip6Address
)
{
LIST_ENTRY *Entry;
IP6_DEFAULT_ROUTER *DefaultRouter;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ASSERT (Ip6Address != NULL);
NET_LIST_FOR_EACH (Entry, &IpSb->DefaultRouterList) {
DefaultRouter = NET_LIST_USER_STRUCT (Entry, IP6_DEFAULT_ROUTER, Link);
if (EFI_IP6_EQUAL (Ip6Address, &DefaultRouter->Router)) {
return DefaultRouter;
}
}
return NULL;
}
/**
The function to be called after DAD (Duplicate Address Detection) is performed.
@param[in] IsDadPassed If TRUE, the DAD operation succeed. Otherwise, the DAD operation failed.
@param[in] IpIf Points to the IP6_INTERFACE.
@param[in] DadEntry The DAD entry which already performed DAD.
**/
VOID
Ip6OnDADFinished (
IN BOOLEAN IsDadPassed,
IN IP6_INTERFACE *IpIf,
IN IP6_DAD_ENTRY *DadEntry
)
{
IP6_SERVICE *IpSb;
IP6_ADDRESS_INFO *AddrInfo;
EFI_DHCP6_PROTOCOL *Dhcp6;
UINT16 OptBuf[4];
EFI_DHCP6_PACKET_OPTION *Oro;
EFI_DHCP6_RETRANSMISSION InfoReqReXmit;
EFI_IPv6_ADDRESS AllNodes;
IpSb = IpIf->Service;
AddrInfo = DadEntry->AddressInfo;
if (IsDadPassed) {
//
// DAD succeed.
//
if (NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
ASSERT (!IpSb->LinkLocalOk);
IP6_COPY_ADDRESS (&IpSb->LinkLocalAddr, &AddrInfo->Address);
IpSb->LinkLocalOk = TRUE;
IpIf->Configured = TRUE;
//
// Check whether DHCP6 need to be started.
//
Dhcp6 = IpSb->Ip6ConfigInstance.Dhcp6;
if (IpSb->Dhcp6NeedStart) {
Dhcp6->Start (Dhcp6);
IpSb->Dhcp6NeedStart = FALSE;
}
if (IpSb->Dhcp6NeedInfoRequest) {
//
// Set the exta options to send. Here we only want the option request option
// with DNS SERVERS.
//
Oro = (EFI_DHCP6_PACKET_OPTION *)OptBuf;
Oro->OpCode = HTONS (DHCP6_OPT_ORO);
Oro->OpLen = HTONS (2);
*((UINT16 *)&Oro->Data[0]) = HTONS (DHCP6_OPT_DNS_SERVERS);
InfoReqReXmit.Irt = 4;
InfoReqReXmit.Mrc = 64;
InfoReqReXmit.Mrt = 60;
InfoReqReXmit.Mrd = 0;
Dhcp6->InfoRequest (
Dhcp6,
TRUE,
Oro,
0,
NULL,
&InfoReqReXmit,
IpSb->Ip6ConfigInstance.Dhcp6Event,
Ip6ConfigOnDhcp6Reply,
&IpSb->Ip6ConfigInstance
);
}
//
// Add an on-link prefix for link-local address.
//
Ip6CreatePrefixListEntry (
IpSb,
TRUE,
(UINT32)IP6_INFINIT_LIFETIME,
(UINT32)IP6_INFINIT_LIFETIME,
IP6_LINK_LOCAL_PREFIX_LENGTH,
&IpSb->LinkLocalAddr
);
} else {
//
// Global scope unicast address.
//
Ip6AddAddr (IpIf, AddrInfo);
//
// Add an on-link prefix for this address.
//
Ip6CreatePrefixListEntry (
IpSb,
TRUE,
AddrInfo->ValidLifetime,
AddrInfo->PreferredLifetime,
AddrInfo->PrefixLength,
&AddrInfo->Address
);
IpIf->Configured = TRUE;
}
} else {
//
// Leave the group we joined before.
//
Ip6LeaveGroup (IpSb, &DadEntry->Destination);
}
if (DadEntry->Callback != NULL) {
DadEntry->Callback (IsDadPassed, &AddrInfo->Address, DadEntry->Context);
}
if (!IsDadPassed && NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
FreePool (AddrInfo);
RemoveEntryList (&DadEntry->Link);
FreePool (DadEntry);
//
// Leave link-scope all-nodes multicast address (FF02::1)
//
Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes);
Ip6LeaveGroup (IpSb, &AllNodes);
//
// Disable IP operation since link-local address is a duplicate address.
//
IpSb->LinkLocalDadFail = TRUE;
IpSb->Mnp->Configure (IpSb->Mnp, NULL);
gBS->SetTimer (IpSb->Timer, TimerCancel, 0);
gBS->SetTimer (IpSb->FasterTimer, TimerCancel, 0);
return;
}
if (!IsDadPassed || NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
//
// Free the AddressInfo we hold if DAD fails or it is a link-local address.
//
FreePool (AddrInfo);
}
RemoveEntryList (&DadEntry->Link);
FreePool (DadEntry);
}
/**
Create a DAD (Duplicate Address Detection) entry and queue it to be performed.
@param[in] IpIf Points to the IP6_INTERFACE.
@param[in] AddressInfo The address information which needs DAD performed.
@param[in] Callback The callback routine that will be called after DAD
is performed. This is an optional parameter that
may be NULL.
@param[in] Context The opaque parameter for a DAD callback routine.
This is an optional parameter that may be NULL.
@retval EFI_SUCCESS The DAD entry was created and queued.
@retval EFI_OUT_OF_RESOURCES Failed to allocate the memory to complete the
operation.
**/
EFI_STATUS
Ip6InitDADProcess (
IN IP6_INTERFACE *IpIf,
IN IP6_ADDRESS_INFO *AddressInfo,
IN IP6_DAD_CALLBACK Callback OPTIONAL,
IN VOID *Context OPTIONAL
)
{
IP6_DAD_ENTRY *Entry;
EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS *DadXmits;
IP6_SERVICE *IpSb;
EFI_STATUS Status;
UINT32 MaxDelayTick;
NET_CHECK_SIGNATURE (IpIf, IP6_INTERFACE_SIGNATURE);
ASSERT (AddressInfo != NULL);
//
// Do nothing if we have already started DAD on the address.
//
if (Ip6FindDADEntry (IpIf->Service, &AddressInfo->Address, NULL) != NULL) {
return EFI_SUCCESS;
}
Status = EFI_SUCCESS;
IpSb = IpIf->Service;
DadXmits = &IpSb->Ip6ConfigInstance.DadXmits;
//
// Allocate the resources and insert info
//
Entry = AllocatePool (sizeof (IP6_DAD_ENTRY));
if (Entry == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Map the incoming unicast address to solicited-node multicast address
//
Ip6CreateSNMulticastAddr (&AddressInfo->Address, &Entry->Destination);
//
// Join in the solicited-node multicast address.
//
Status = Ip6JoinGroup (IpSb, IpIf, &Entry->Destination);
if (EFI_ERROR (Status)) {
FreePool (Entry);
return Status;
}
Entry->Signature = IP6_DAD_ENTRY_SIGNATURE;
Entry->MaxTransmit = DadXmits->DupAddrDetectTransmits;
Entry->Transmit = 0;
Entry->Receive = 0;
MaxDelayTick = IP6_MAX_RTR_SOLICITATION_DELAY / IP6_TIMER_INTERVAL_IN_MS;
Entry->RetransTick = (MaxDelayTick * ((NET_RANDOM (NetRandomInitSeed ()) % 5) + 1)) / 5;
Entry->AddressInfo = AddressInfo;
Entry->Callback = Callback;
Entry->Context = Context;
InsertTailList (&IpIf->DupAddrDetectList, &Entry->Link);
if (Entry->MaxTransmit == 0) {
//
// DAD is disabled on this interface, immediately mark this DAD successful.
//
Ip6OnDADFinished (TRUE, IpIf, Entry);
}
return EFI_SUCCESS;
}
/**
Search IP6_DAD_ENTRY from the Duplicate Address Detection List.
@param[in] IpSb The pointer to the IP6_SERVICE instance.
@param[in] Target The address information which needs DAD performed .
@param[out] Interface If not NULL, output the IP6 interface that configures
the tentative address.
@return NULL if failed to find the matching DAD entry.
Otherwise, point to the found DAD entry.
**/
IP6_DAD_ENTRY *
Ip6FindDADEntry (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *Target,
OUT IP6_INTERFACE **Interface OPTIONAL
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Entry2;
IP6_INTERFACE *IpIf;
IP6_DAD_ENTRY *DupAddrDetect;
IP6_ADDRESS_INFO *AddrInfo;
NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link);
NET_LIST_FOR_EACH (Entry2, &IpIf->DupAddrDetectList) {
DupAddrDetect = NET_LIST_USER_STRUCT_S (Entry2, IP6_DAD_ENTRY, Link, IP6_DAD_ENTRY_SIGNATURE);
AddrInfo = DupAddrDetect->AddressInfo;
if (EFI_IP6_EQUAL (&AddrInfo->Address, Target)) {
if (Interface != NULL) {
*Interface = IpIf;
}
return DupAddrDetect;
}
}
}
return NULL;
}
/**
Generate router solicit message and send it out to Destination Address or
All Router Link Local scope multicast address.
@param[in] IpSb The IP service to send the packet.
@param[in] Interface If not NULL, points to the IP6 interface to send
the packet.
@param[in] SourceAddress If not NULL, the source address of the message.
@param[in] DestinationAddress If not NULL, the destination address of the message.
@param[in] SourceLinkAddress If not NULL, the MAC address of the source.
A source link-layer address option will be appended
to the message.
@retval EFI_OUT_OF_RESOURCES Insufficient resources to complete the
operation.
@retval EFI_SUCCESS The router solicit message was successfully sent.
**/
EFI_STATUS
Ip6SendRouterSolicit (
IN IP6_SERVICE *IpSb,
IN IP6_INTERFACE *Interface OPTIONAL,
IN EFI_IPv6_ADDRESS *SourceAddress OPTIONAL,
IN EFI_IPv6_ADDRESS *DestinationAddress OPTIONAL,
IN EFI_MAC_ADDRESS *SourceLinkAddress OPTIONAL
)
{
NET_BUF *Packet;
EFI_IP6_HEADER Head;
IP6_ICMP_INFORMATION_HEAD *IcmpHead;
IP6_ETHER_ADDR_OPTION *LinkLayerOption;
UINT16 PayloadLen;
IP6_INTERFACE *IpIf;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
IpIf = Interface;
if ((IpIf == NULL) && (IpSb->DefaultInterface != NULL)) {
IpIf = IpSb->DefaultInterface;
}
//
// Generate the packet to be sent
//
PayloadLen = (UINT16)sizeof (IP6_ICMP_INFORMATION_HEAD);
if (SourceLinkAddress != NULL) {
PayloadLen += sizeof (IP6_ETHER_ADDR_OPTION);
}
Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32)PayloadLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Create the basic IPv6 header.
//
Head.FlowLabelL = 0;
Head.FlowLabelH = 0;
Head.PayloadLength = HTONS (PayloadLen);
Head.NextHeader = IP6_ICMP;
Head.HopLimit = IP6_HOP_LIMIT;
if (SourceAddress != NULL) {
IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
} else {
ZeroMem (&Head.SourceAddress, sizeof (EFI_IPv6_ADDRESS));
}
if (DestinationAddress != NULL) {
IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);
} else {
Ip6SetToAllNodeMulticast (TRUE, IP6_LINK_LOCAL_SCOPE, &Head.DestinationAddress);
}
NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
//
// Fill in the ICMP header, and Source link-layer address if contained.
//
IcmpHead = (IP6_ICMP_INFORMATION_HEAD *)NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
ASSERT (IcmpHead != NULL);
ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
IcmpHead->Head.Type = ICMP_V6_ROUTER_SOLICIT;
IcmpHead->Head.Code = 0;
LinkLayerOption = NULL;
if (SourceLinkAddress != NULL) {
LinkLayerOption = (IP6_ETHER_ADDR_OPTION *)NetbufAllocSpace (
Packet,
sizeof (IP6_ETHER_ADDR_OPTION),
FALSE
);
ASSERT (LinkLayerOption != NULL);
LinkLayerOption->Type = Ip6OptionEtherSource;
LinkLayerOption->Length = (UINT8)sizeof (IP6_ETHER_ADDR_OPTION);
CopyMem (LinkLayerOption->EtherAddr, SourceLinkAddress, 6);
}
//
// Transmit the packet
//
return Ip6Output (IpSb, IpIf, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}
/**
Generate a Neighbor Advertisement message and send it out to Destination Address.
@param[in] IpSb The IP service to send the packet.
@param[in] SourceAddress The source address of the message.
@param[in] DestinationAddress The destination address of the message.
@param[in] TargetIp6Address The target address field in the Neighbor Solicitation
message that prompted this advertisement.
@param[in] TargetLinkAddress The MAC address for the target, i.e. the sender
of the advertisement.
@param[in] IsRouter If TRUE, indicates the sender is a router.
@param[in] Override If TRUE, indicates the advertisement should override
an existing cache entry and update the MAC address.
@param[in] Solicited If TRUE, indicates the advertisement was sent
in response to a Neighbor Solicitation from
the Destination address.
@retval EFI_OUT_OF_RESOURCES Insufficient resources to complete the
operation.
@retval EFI_SUCCESS The Neighbor Advertise message was successfully sent.
**/
EFI_STATUS
Ip6SendNeighborAdvertise (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *SourceAddress,
IN EFI_IPv6_ADDRESS *DestinationAddress,
IN EFI_IPv6_ADDRESS *TargetIp6Address,
IN EFI_MAC_ADDRESS *TargetLinkAddress,
IN BOOLEAN IsRouter,
IN BOOLEAN Override,
IN BOOLEAN Solicited
)
{
NET_BUF *Packet;
EFI_IP6_HEADER Head;
IP6_ICMP_INFORMATION_HEAD *IcmpHead;
IP6_ETHER_ADDR_OPTION *LinkLayerOption;
EFI_IPv6_ADDRESS *Target;
UINT16 PayloadLen;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
//
// The Neighbor Advertisement message must include a Target link-layer address option
// when responding to multicast solicitation and should include such option when
// responding to unicast solicitation. It also must include such option as unsolicited
// advertisement.
//
ASSERT (DestinationAddress != NULL && TargetIp6Address != NULL && TargetLinkAddress != NULL);
PayloadLen = (UINT16)(sizeof (IP6_ICMP_INFORMATION_HEAD) + sizeof (EFI_IPv6_ADDRESS) + sizeof (IP6_ETHER_ADDR_OPTION));
//
// Generate the packet to be sent
//
Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32)PayloadLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Create the basic IPv6 header.
//
Head.FlowLabelL = 0;
Head.FlowLabelH = 0;
Head.PayloadLength = HTONS (PayloadLen);
Head.NextHeader = IP6_ICMP;
Head.HopLimit = IP6_HOP_LIMIT;
IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);
NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
//
// Fill in the ICMP header, Target address, and Target link-layer address.
// Set the Router flag, Solicited flag and Override flag.
//
IcmpHead = (IP6_ICMP_INFORMATION_HEAD *)NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
ASSERT (IcmpHead != NULL);
ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
IcmpHead->Head.Type = ICMP_V6_NEIGHBOR_ADVERTISE;
IcmpHead->Head.Code = 0;
if (IsRouter) {
IcmpHead->Fourth |= IP6_IS_ROUTER_FLAG;
}
if (Solicited) {
IcmpHead->Fourth |= IP6_SOLICITED_FLAG;
}
if (Override) {
IcmpHead->Fourth |= IP6_OVERRIDE_FLAG;
}
Target = (EFI_IPv6_ADDRESS *)NetbufAllocSpace (Packet, sizeof (EFI_IPv6_ADDRESS), FALSE);
ASSERT (Target != NULL);
IP6_COPY_ADDRESS (Target, TargetIp6Address);
LinkLayerOption = (IP6_ETHER_ADDR_OPTION *)NetbufAllocSpace (
Packet,
sizeof (IP6_ETHER_ADDR_OPTION),
FALSE
);
ASSERT (LinkLayerOption != NULL);
LinkLayerOption->Type = Ip6OptionEtherTarget;
LinkLayerOption->Length = 1;
CopyMem (LinkLayerOption->EtherAddr, TargetLinkAddress, 6);
//
// Transmit the packet
//
return Ip6Output (IpSb, NULL, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}
/**
Generate the Neighbor Solicitation message and send it to the Destination Address.
@param[in] IpSb The IP service to send the packet
@param[in] SourceAddress The source address of the message.
@param[in] DestinationAddress The destination address of the message.
@param[in] TargetIp6Address The IP address of the target of the solicitation.
It must not be a multicast address.
@param[in] SourceLinkAddress The MAC address for the sender. If not NULL,
a source link-layer address option will be appended
to the message.
@retval EFI_INVALID_PARAMETER Any input parameter is invalid.
@retval EFI_OUT_OF_RESOURCES Insufficient resources to complete the
operation.
@retval EFI_SUCCESS The Neighbor Advertise message was successfully sent.
**/
EFI_STATUS
Ip6SendNeighborSolicit (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *SourceAddress,
IN EFI_IPv6_ADDRESS *DestinationAddress,
IN EFI_IPv6_ADDRESS *TargetIp6Address,
IN EFI_MAC_ADDRESS *SourceLinkAddress OPTIONAL
)
{
NET_BUF *Packet;
EFI_IP6_HEADER Head;
IP6_ICMP_INFORMATION_HEAD *IcmpHead;
IP6_ETHER_ADDR_OPTION *LinkLayerOption;
EFI_IPv6_ADDRESS *Target;
BOOLEAN IsDAD;
UINT16 PayloadLen;
IP6_NEIGHBOR_ENTRY *Neighbor;
//
// Check input parameters
//
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
if ((DestinationAddress == NULL) || (TargetIp6Address == NULL)) {
return EFI_INVALID_PARAMETER;
}
IsDAD = FALSE;
if ((SourceAddress == NULL) || ((SourceAddress != NULL) && NetIp6IsUnspecifiedAddr (SourceAddress))) {
IsDAD = TRUE;
}
//
// The Neighbor Solicitation message should include a source link-layer address option
// if the solicitation is not sent by performing DAD - Duplicate Address Detection.
// Otherwise must not include it.
//
PayloadLen = (UINT16)(sizeof (IP6_ICMP_INFORMATION_HEAD) + sizeof (EFI_IPv6_ADDRESS));
if (!IsDAD) {
if (SourceLinkAddress == NULL) {
return EFI_INVALID_PARAMETER;
}
PayloadLen = (UINT16)(PayloadLen + sizeof (IP6_ETHER_ADDR_OPTION));
}
//
// Generate the packet to be sent
//
Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32)PayloadLen);
if (Packet == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Create the basic IPv6 header
//
Head.FlowLabelL = 0;
Head.FlowLabelH = 0;
Head.PayloadLength = HTONS (PayloadLen);
Head.NextHeader = IP6_ICMP;
Head.HopLimit = IP6_HOP_LIMIT;
if (SourceAddress != NULL) {
IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
} else {
ZeroMem (&Head.SourceAddress, sizeof (EFI_IPv6_ADDRESS));
}
IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);
NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
//
// Fill in the ICMP header, Target address, and Source link-layer address.
//
IcmpHead = (IP6_ICMP_INFORMATION_HEAD *)NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
ASSERT (IcmpHead != NULL);
ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
IcmpHead->Head.Type = ICMP_V6_NEIGHBOR_SOLICIT;
IcmpHead->Head.Code = 0;
Target = (EFI_IPv6_ADDRESS *)NetbufAllocSpace (Packet, sizeof (EFI_IPv6_ADDRESS), FALSE);
ASSERT (Target != NULL);
IP6_COPY_ADDRESS (Target, TargetIp6Address);
LinkLayerOption = NULL;
if (!IsDAD) {
//
// Fill in the source link-layer address option
//
LinkLayerOption = (IP6_ETHER_ADDR_OPTION *)NetbufAllocSpace (
Packet,
sizeof (IP6_ETHER_ADDR_OPTION),
FALSE
);
ASSERT (LinkLayerOption != NULL);
LinkLayerOption->Type = Ip6OptionEtherSource;
LinkLayerOption->Length = 1;
CopyMem (LinkLayerOption->EtherAddr, SourceLinkAddress, 6);
}
//
// Create a Neighbor Cache entry in the INCOMPLETE state when performing
// address resolution.
//
if (!IsDAD && Ip6IsSNMulticastAddr (DestinationAddress)) {
Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
if (Neighbor == NULL) {
Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, TargetIp6Address, NULL);
ASSERT (Neighbor != NULL);
}
}
//
// Transmit the packet
//
return Ip6Output (IpSb, IpSb->DefaultInterface, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}
/**
Process the Neighbor Solicitation message. The message may be sent for Duplicate
Address Detection or Address Resolution.
@param[in] IpSb The IP service that received the packet.
@param[in] Head The IP head of the message.
@param[in] Packet The content of the message with IP head removed.
@retval EFI_SUCCESS The packet processed successfully.
@retval EFI_INVALID_PARAMETER The packet is invalid.
@retval EFI_ICMP_ERROR The packet indicates that DAD is failed.
@retval Others Failed to process the packet.
**/
EFI_STATUS
Ip6ProcessNeighborSolicit (
IN IP6_SERVICE *IpSb,
IN EFI_IP6_HEADER *Head,
IN NET_BUF *Packet
)
{
IP6_ICMP_INFORMATION_HEAD Icmp;
EFI_IPv6_ADDRESS Target;
IP6_ETHER_ADDR_OPTION LinkLayerOption;
BOOLEAN IsDAD;
BOOLEAN IsUnicast;
BOOLEAN IsMaintained;
IP6_DAD_ENTRY *DupAddrDetect;
IP6_INTERFACE *IpIf;
IP6_NEIGHBOR_ENTRY *Neighbor;
BOOLEAN Solicited;
BOOLEAN UpdateCache;
EFI_IPv6_ADDRESS Dest;
UINT16 OptionLen;
UINT8 *Option;
BOOLEAN Provided;
EFI_STATUS Status;
VOID *MacAddress;
NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);
NetbufCopy (Packet, sizeof (Icmp), sizeof (Target), Target.Addr);
//
// Perform Message Validation:
// The IP Hop Limit field has a value of 255, i.e., the packet
// could not possibly have been forwarded by a router.
// ICMP Code is 0.
// Target Address is not a multicast address.
//
Status = EFI_INVALID_PARAMETER;
if ((Head->HopLimit != IP6_HOP_LIMIT) || (Icmp.Head.Code != 0) || !NetIp6IsValidUnicast (&Target)) {
goto Exit;
}
//
// ICMP length is 24 or more octets.
//
OptionLen = 0;
if (Head->PayloadLength < IP6_ND_LENGTH) {
goto Exit;
} else {
OptionLen = (UINT16)(Head->PayloadLength - IP6_ND_LENGTH);
if (OptionLen != 0) {
Option = NetbufGetByte (Packet, IP6_ND_LENGTH, NULL);
ASSERT (Option != NULL);
//
// All included options should have a length that is greater than zero.
//
if (!Ip6IsNDOptionValid (Option, OptionLen)) {
goto Exit;
}
}
}
IsDAD = NetIp6IsUnspecifiedAddr (&Head->SourceAddress);
IsUnicast = (BOOLEAN) !Ip6IsSNMulticastAddr (&Head->DestinationAddress);
IsMaintained = Ip6IsOneOfSetAddress (IpSb, &Target, &IpIf, NULL);
Provided = FALSE;
if (OptionLen >= sizeof (IP6_ETHER_ADDR_OPTION)) {
NetbufCopy (
Packet,
IP6_ND_LENGTH,
sizeof (IP6_ETHER_ADDR_OPTION),
(UINT8 *)&LinkLayerOption
);
//
// The solicitation for neighbor discovery should include a source link-layer
// address option. If the option is not recognized, silently ignore it.
//
if (LinkLayerOption.Type == Ip6OptionEtherSource) {
if (IsDAD) {
//
// If the IP source address is the unspecified address, the source
// link-layer address option must not be included in the message.
//
goto Exit;
}
Provided = TRUE;
}
}
//
// If the IP source address is the unspecified address, the IP
// destination address is a solicited-node multicast address.
//
if (IsDAD && IsUnicast) {
goto Exit;
}
//
// If the target address is tentative, and the source address is a unicast address,
// the solicitation's sender is performing address resolution on the target;
// the solicitation should be silently ignored.
//
if (!IsDAD && !IsMaintained) {
goto Exit;
}
//
// If received unicast neighbor solicitation but destination is not this node,
// drop the packet.
//
if (IsUnicast && !IsMaintained) {
goto Exit;
}
//
// In DAD, when target address is a tentative address,
// process the received neighbor solicitation message but not send out response.
//
if (IsDAD && !IsMaintained) {
DupAddrDetect = Ip6FindDADEntry (IpSb, &Target, &IpIf);
if (DupAddrDetect != NULL) {
//
// Check the MAC address of the incoming packet.
//
if (IpSb->RecvRequest.MnpToken.Packet.RxData == NULL) {
goto Exit;
}
MacAddress = IpSb->RecvRequest.MnpToken.Packet.RxData->SourceAddress;
if (MacAddress != NULL) {
if (CompareMem (
MacAddress,
&IpSb->SnpMode.CurrentAddress,
IpSb->SnpMode.HwAddressSize
) != 0)
{
//
// The NS is from another node to performing DAD on the same address.
// Fail DAD for the tentative address.
//
Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
Status = EFI_ICMP_ERROR;
} else {
//
// The below layer loopback the NS we sent. Record it and wait for more.
//
DupAddrDetect->Receive++;
Status = EFI_SUCCESS;
}
}
}
goto Exit;
}
//
// If the solicitation does not contain a link-layer address, DO NOT create or
// update the neighbor cache entries.
//
if (Provided) {
Neighbor = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
UpdateCache = FALSE;
if (Neighbor == NULL) {
Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, &Head->SourceAddress, NULL);
if (Neighbor == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
UpdateCache = TRUE;
} else {
if (CompareMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6) != 0) {
UpdateCache = TRUE;
}
}
if (UpdateCache) {
Neighbor->State = EfiNeighborStale;
Neighbor->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
//
// Send queued packets if exist.
//
Neighbor->CallBack ((VOID *)Neighbor);
}
}
//
// Sends a Neighbor Advertisement as response.
// Set the Router flag to zero since the node is a host.
// If the source address of the solicitation is unspecified, and target address
// is one of the maintained address, reply a unsolicited multicast advertisement.
//
if (IsDAD && IsMaintained) {
Solicited = FALSE;
Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &Dest);
} else {
Solicited = TRUE;
IP6_COPY_ADDRESS (&Dest, &Head->SourceAddress);
}
Status = Ip6SendNeighborAdvertise (
IpSb,
&Target,
&Dest,
&Target,
&IpSb->SnpMode.CurrentAddress,
FALSE,
TRUE,
Solicited
);
Exit:
NetbufFree (Packet);
return Status;
}
/**
Process the Neighbor Advertisement message.
@param[in] IpSb The IP service that received the packet.
@param[in] Head The IP head of the message.
@param[in] Packet The content of the message with IP head removed.
@retval EFI_SUCCESS The packet processed successfully.
@retval EFI_INVALID_PARAMETER The packet is invalid.
@retval EFI_ICMP_ERROR The packet indicates that DAD is failed.
@retval Others Failed to process the packet.
**/
EFI_STATUS
Ip6ProcessNeighborAdvertise (
IN IP6_SERVICE *IpSb,
IN EFI_IP6_HEADER *Head,
IN NET_BUF *Packet
)
{
IP6_ICMP_INFORMATION_HEAD Icmp;
EFI_IPv6_ADDRESS Target;
IP6_ETHER_ADDR_OPTION LinkLayerOption;
BOOLEAN Provided;
INTN Compare;
IP6_NEIGHBOR_ENTRY *Neighbor;
IP6_DEFAULT_ROUTER *DefaultRouter;
BOOLEAN Solicited;
BOOLEAN IsRouter;
BOOLEAN Override;
IP6_DAD_ENTRY *DupAddrDetect;
IP6_INTERFACE *IpIf;
UINT16 OptionLen;
UINT8 *Option;
EFI_STATUS Status;
NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);
NetbufCopy (Packet, sizeof (Icmp), sizeof (Target), Target.Addr);
//
// Validate the incoming Neighbor Advertisement
//
Status = EFI_INVALID_PARAMETER;
//
// The IP Hop Limit field has a value of 255, i.e., the packet
// could not possibly have been forwarded by a router.
// ICMP Code is 0.
// Target Address is not a multicast address.
//
if ((Head->HopLimit != IP6_HOP_LIMIT) || (Icmp.Head.Code != 0) || !NetIp6IsValidUnicast (&Target)) {
goto Exit;
}
//
// ICMP length is 24 or more octets.
//
Provided = FALSE;
OptionLen = 0;
if (Head->PayloadLength < IP6_ND_LENGTH) {
goto Exit;
} else {
OptionLen = (UINT16)(Head->PayloadLength - IP6_ND_LENGTH);
if (OptionLen != 0) {
Option = NetbufGetByte (Packet, IP6_ND_LENGTH, NULL);
ASSERT (Option != NULL);
//
// All included options should have a length that is greater than zero.
//
if (!Ip6IsNDOptionValid (Option, OptionLen)) {
goto Exit;
}
}
}
//
// If the IP destination address is a multicast address, Solicited Flag is ZERO.
//
Solicited = FALSE;
if ((Icmp.Fourth & IP6_SOLICITED_FLAG) == IP6_SOLICITED_FLAG) {
Solicited = TRUE;
}
if (IP6_IS_MULTICAST (&Head->DestinationAddress) && Solicited) {
goto Exit;
}
//
// DAD - Check whether the Target is one of our tentative address.
//
DupAddrDetect = Ip6FindDADEntry (IpSb, &Target, &IpIf);
if (DupAddrDetect != NULL) {
//
// DAD fails, some other node is using this address.
//
NetbufFree (Packet);
Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
return EFI_ICMP_ERROR;
}
//
// Search the Neighbor Cache for the target's entry. If no entry exists,
// the advertisement should be silently discarded.
//
Neighbor = Ip6FindNeighborEntry (IpSb, &Target);
if (Neighbor == NULL) {
goto Exit;
}
//
// Get IsRouter Flag and Override Flag
//
IsRouter = FALSE;
Override = FALSE;
if ((Icmp.Fourth & IP6_IS_ROUTER_FLAG) == IP6_IS_ROUTER_FLAG) {
IsRouter = TRUE;
}
if ((Icmp.Fourth & IP6_OVERRIDE_FLAG) == IP6_OVERRIDE_FLAG) {
Override = TRUE;
}
//
// Check whether link layer option is included.
//
if (OptionLen >= sizeof (IP6_ETHER_ADDR_OPTION)) {
NetbufCopy (
Packet,
IP6_ND_LENGTH,
sizeof (IP6_ETHER_ADDR_OPTION),
(UINT8 *)&LinkLayerOption
);
if (LinkLayerOption.Type == Ip6OptionEtherTarget) {
Provided = TRUE;
}
}
Compare = 0;
if (Provided) {
Compare = CompareMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
}
if (!Neighbor->IsRouter && IsRouter) {
DefaultRouter = Ip6FindDefaultRouter (IpSb, &Target);
if (DefaultRouter != NULL) {
DefaultRouter->NeighborCache = Neighbor;
}
}
if (Neighbor->State == EfiNeighborInComplete) {
//
// If the target's Neighbor Cache entry is in INCOMPLETE state and no
// Target Link-Layer address option is included while link layer has
// address, the message should be silently discarded.
//
if (!Provided) {
goto Exit;
}
//
// Update the Neighbor Cache
//
CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
if (Solicited) {
Neighbor->State = EfiNeighborReachable;
Neighbor->Ticks = IP6_GET_TICKS (IpSb->ReachableTime);
} else {
Neighbor->State = EfiNeighborStale;
Neighbor->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
//
// Send any packets queued for the neighbor awaiting address resolution.
//
Neighbor->CallBack ((VOID *)Neighbor);
}
Neighbor->IsRouter = IsRouter;
} else {
if (!Override && (Compare != 0)) {
//
// When the Override Flag is clear and supplied link-layer address differs from
// that in the cache, if the state of the entry is not REACHABLE, ignore the
// message. Otherwise set it to STALE but do not update the entry in any
// other way.
//
if (Neighbor->State == EfiNeighborReachable) {
Neighbor->State = EfiNeighborStale;
Neighbor->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
}
} else {
if (Compare != 0) {
CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
}
//
// Update the entry's state
//
if (Solicited) {
Neighbor->State = EfiNeighborReachable;
Neighbor->Ticks = IP6_GET_TICKS (IpSb->ReachableTime);
} else {
if (Compare != 0) {
Neighbor->State = EfiNeighborStale;
Neighbor->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
}
}
//
// When IsRouter is changed from TRUE to FALSE, remove the router from the
// Default Router List and remove the Destination Cache entries for all destinations
// using the neighbor as a router.
//
if (Neighbor->IsRouter && !IsRouter) {
DefaultRouter = Ip6FindDefaultRouter (IpSb, &Target);
if (DefaultRouter != NULL) {
Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
}
}
Neighbor->IsRouter = IsRouter;
}
}
if (Neighbor->State == EfiNeighborReachable) {
Neighbor->CallBack ((VOID *)Neighbor);
}
Status = EFI_SUCCESS;
Exit:
NetbufFree (Packet);
return Status;
}
/**
Process the Router Advertisement message according to RFC4861.
@param[in] IpSb The IP service that received the packet.
@param[in] Head The IP head of the message.
@param[in] Packet The content of the message with the IP head removed.
@retval EFI_SUCCESS The packet processed successfully.
@retval EFI_INVALID_PARAMETER The packet is invalid.
@retval EFI_OUT_OF_RESOURCES Insufficient resources to complete the
operation.
@retval Others Failed to process the packet.
**/
EFI_STATUS
Ip6ProcessRouterAdvertise (
IN IP6_SERVICE *IpSb,
IN EFI_IP6_HEADER *Head,
IN NET_BUF *Packet
)
{
IP6_ICMP_INFORMATION_HEAD Icmp;
UINT32 ReachableTime;
UINT32 RetransTimer;
UINT16 RouterLifetime;
UINT32 Offset;
UINT8 Type;
UINT8 Length;
IP6_ETHER_ADDR_OPTION LinkLayerOption;
UINT32 Fourth;
UINT8 CurHopLimit;
BOOLEAN Mflag;
BOOLEAN Oflag;
IP6_DEFAULT_ROUTER *DefaultRouter;
IP6_NEIGHBOR_ENTRY *NeighborCache;
EFI_MAC_ADDRESS LinkLayerAddress;
IP6_MTU_OPTION MTUOption;
IP6_PREFIX_INFO_OPTION PrefixOption;
IP6_PREFIX_LIST_ENTRY *PrefixList;
BOOLEAN OnLink;
BOOLEAN Autonomous;
EFI_IPv6_ADDRESS StatelessAddress;
EFI_STATUS Status;
UINT16 OptionLen;
UINT8 *Option;
INTN Result;
Status = EFI_INVALID_PARAMETER;
if (IpSb->Ip6ConfigInstance.Policy != Ip6ConfigPolicyAutomatic) {
//
// Skip the process below as it's not required under the current policy.
//
goto Exit;
}
NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);
//
// Validate the incoming Router Advertisement
//
//
// The IP source address must be a link-local address
//
if (!NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
goto Exit;
}
//
// The IP Hop Limit field has a value of 255, i.e. the packet
// could not possibly have been forwarded by a router.
// ICMP Code is 0.
// ICMP length (derived from the IP length) is 16 or more octets.
//
if ((Head->HopLimit != IP6_HOP_LIMIT) || (Icmp.Head.Code != 0) ||
(Head->PayloadLength < IP6_RA_LENGTH))
{
goto Exit;
}
//
// All included options have a length that is greater than zero.
//
OptionLen = (UINT16)(Head->PayloadLength - IP6_RA_LENGTH);
if (OptionLen != 0) {
Option = NetbufGetByte (Packet, IP6_RA_LENGTH, NULL);
ASSERT (Option != NULL);
if (!Ip6IsNDOptionValid (Option, OptionLen)) {
goto Exit;
}
}
//
// Process Fourth field.
// In Router Advertisement, Fourth is composed of CurHopLimit (8bit), M flag, O flag,
// and Router Lifetime (16 bit).
//
Fourth = NTOHL (Icmp.Fourth);
CopyMem (&RouterLifetime, &Fourth, sizeof (UINT16));
//
// If the source address already in the default router list, update it.
// Otherwise create a new entry.
// A Lifetime of zero indicates that the router is not a default router.
//
DefaultRouter = Ip6FindDefaultRouter (IpSb, &Head->SourceAddress);
if (DefaultRouter == NULL) {
if (RouterLifetime != 0) {
DefaultRouter = Ip6CreateDefaultRouter (IpSb, &Head->SourceAddress, RouterLifetime);
if (DefaultRouter == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
}
} else {
if (RouterLifetime != 0) {
DefaultRouter->Lifetime = RouterLifetime;
//
// Check the corresponding neighbor cache entry here.
//
if (DefaultRouter->NeighborCache == NULL) {
DefaultRouter->NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
}
} else {
//
// If the address is in the host's default router list and the router lifetime is zero,
// immediately time-out the entry.
//
Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
}
}
CurHopLimit = *((UINT8 *)&Fourth + 3);
if (CurHopLimit != 0) {
IpSb->CurHopLimit = CurHopLimit;
}
Mflag = FALSE;
Oflag = FALSE;
if ((*((UINT8 *)&Fourth + 2) & IP6_M_ADDR_CONFIG_FLAG) == IP6_M_ADDR_CONFIG_FLAG) {
Mflag = TRUE;
} else {
if ((*((UINT8 *)&Fourth + 2) & IP6_O_CONFIG_FLAG) == IP6_O_CONFIG_FLAG) {
Oflag = TRUE;
}
}
if (Mflag || Oflag) {
//
// Use Ip6Config to get available addresses or other configuration from DHCP.
//
Ip6ConfigStartStatefulAutoConfig (&IpSb->Ip6ConfigInstance, Oflag);
}
//
// Process Reachable Time and Retrans Timer fields.
//
NetbufCopy (Packet, sizeof (Icmp), sizeof (UINT32), (UINT8 *)&ReachableTime);
NetbufCopy (Packet, sizeof (Icmp) + sizeof (UINT32), sizeof (UINT32), (UINT8 *)&RetransTimer);
ReachableTime = NTOHL (ReachableTime);
RetransTimer = NTOHL (RetransTimer);
if ((ReachableTime != 0) && (ReachableTime != IpSb->BaseReachableTime)) {
//
// If new value is not unspecified and differs from the previous one, record it
// in BaseReachableTime and recompute a ReachableTime.
//
IpSb->BaseReachableTime = ReachableTime;
Ip6UpdateReachableTime (IpSb);
}
if (RetransTimer != 0) {
IpSb->RetransTimer = RetransTimer;
}
//
// IsRouter flag must be set to TRUE if corresponding neighbor cache entry exists.
//
NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
if (NeighborCache != NULL) {
NeighborCache->IsRouter = TRUE;
}
//
// If an valid router advertisement is received, stops router solicitation.
//
IpSb->RouterAdvertiseReceived = TRUE;
//
// The only defined options that may appear are the Source
// Link-Layer Address, Prefix information and MTU options.
// All included options have a length that is greater than zero and
// fit within the input packet.
//
Offset = 16;
while (Offset < (UINT32)Head->PayloadLength) {
NetbufCopy (Packet, Offset, sizeof (UINT8), &Type);
switch (Type) {
case Ip6OptionEtherSource:
//
// Update the neighbor cache
//
NetbufCopy (Packet, Offset, sizeof (IP6_ETHER_ADDR_OPTION), (UINT8 *)&LinkLayerOption);
//
// Option size validity ensured by Ip6IsNDOptionValid().
//
ASSERT (LinkLayerOption.Length != 0);
ASSERT (Offset + (UINT32)LinkLayerOption.Length * 8 <= (UINT32)Head->PayloadLength);
ZeroMem (&LinkLayerAddress, sizeof (EFI_MAC_ADDRESS));
CopyMem (&LinkLayerAddress, LinkLayerOption.EtherAddr, 6);
if (NeighborCache == NULL) {
NeighborCache = Ip6CreateNeighborEntry (
IpSb,
Ip6OnArpResolved,
&Head->SourceAddress,
&LinkLayerAddress
);
if (NeighborCache == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
NeighborCache->IsRouter = TRUE;
NeighborCache->State = EfiNeighborStale;
NeighborCache->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
} else {
Result = CompareMem (&LinkLayerAddress, &NeighborCache->LinkAddress, 6);
//
// If the link-local address is the same as that already in the cache,
// the cache entry's state remains unchanged. Otherwise update the
// reachability state to STALE.
//
if ((NeighborCache->State == EfiNeighborInComplete) || (Result != 0)) {
CopyMem (&NeighborCache->LinkAddress, &LinkLayerAddress, 6);
NeighborCache->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
if (NeighborCache->State == EfiNeighborInComplete) {
//
// Send queued packets if exist.
//
NeighborCache->State = EfiNeighborStale;
NeighborCache->CallBack ((VOID *)NeighborCache);
} else {
NeighborCache->State = EfiNeighborStale;
}
}
}
Offset += (UINT32)LinkLayerOption.Length * 8;
break;
case Ip6OptionPrefixInfo:
NetbufCopy (Packet, Offset, sizeof (IP6_PREFIX_INFO_OPTION), (UINT8 *)&PrefixOption);
//
// Option size validity ensured by Ip6IsNDOptionValid().
//
ASSERT (PrefixOption.Length == 4);
ASSERT (Offset + (UINT32)PrefixOption.Length * 8 <= (UINT32)Head->PayloadLength);
PrefixOption.ValidLifetime = NTOHL (PrefixOption.ValidLifetime);
PrefixOption.PreferredLifetime = NTOHL (PrefixOption.PreferredLifetime);
//
// Get L and A flag, recorded in the lower 2 bits of Reserved1
//
OnLink = FALSE;
if ((PrefixOption.Reserved1 & IP6_ON_LINK_FLAG) == IP6_ON_LINK_FLAG) {
OnLink = TRUE;
}
Autonomous = FALSE;
if ((PrefixOption.Reserved1 & IP6_AUTO_CONFIG_FLAG) == IP6_AUTO_CONFIG_FLAG) {
Autonomous = TRUE;
}
//
// If the prefix is the link-local prefix, silently ignore the prefix option.
//
if ((PrefixOption.PrefixLength == IP6_LINK_LOCAL_PREFIX_LENGTH) &&
NetIp6IsLinkLocalAddr (&PrefixOption.Prefix)
)
{
Offset += sizeof (IP6_PREFIX_INFO_OPTION);
break;
}
//
// Do following if on-link flag is set according to RFC4861.
//
if (OnLink) {
PrefixList = Ip6FindPrefixListEntry (
IpSb,
TRUE,
PrefixOption.PrefixLength,
&PrefixOption.Prefix
);
//
// Create a new entry for the prefix, if the ValidLifetime is zero,
// silently ignore the prefix option.
//
if ((PrefixList == NULL) && (PrefixOption.ValidLifetime != 0)) {
PrefixList = Ip6CreatePrefixListEntry (
IpSb,
TRUE,
PrefixOption.ValidLifetime,
PrefixOption.PreferredLifetime,
PrefixOption.PrefixLength,
&PrefixOption.Prefix
);
if (PrefixList == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
} else if (PrefixList != NULL) {
if (PrefixOption.ValidLifetime != 0) {
PrefixList->ValidLifetime = PrefixOption.ValidLifetime;
} else {
//
// If the prefix exists and incoming ValidLifetime is zero, immediately
// remove the prefix.
Ip6DestroyPrefixListEntry (IpSb, PrefixList, OnLink, TRUE);
}
}
}
//
// Do following if Autonomous flag is set according to RFC4862.
//
if (Autonomous && (PrefixOption.PreferredLifetime <= PrefixOption.ValidLifetime)) {
PrefixList = Ip6FindPrefixListEntry (
IpSb,
FALSE,
PrefixOption.PrefixLength,
&PrefixOption.Prefix
);
//
// Create a new entry for the prefix, and form an address by prefix + interface id
// If the sum of the prefix length and interface identifier length
// does not equal 128 bits, the Prefix Information option MUST be ignored.
//
if ((PrefixList == NULL) &&
(PrefixOption.ValidLifetime != 0) &&
(PrefixOption.PrefixLength + IpSb->InterfaceIdLen * 8 == 128)
)
{
//
// Form the address in network order.
//
CopyMem (&StatelessAddress, &PrefixOption.Prefix, sizeof (UINT64));
CopyMem (&StatelessAddress.Addr[8], IpSb->InterfaceId, sizeof (UINT64));
//
// If the address is not yet in the assigned address list, adds it into.
//
if (!Ip6IsOneOfSetAddress (IpSb, &StatelessAddress, NULL, NULL)) {
//
// And also not in the DAD process, check its uniqueness firstly.
//
if (Ip6FindDADEntry (IpSb, &StatelessAddress, NULL) == NULL) {
Status = Ip6SetAddress (
IpSb->DefaultInterface,
&StatelessAddress,
FALSE,
PrefixOption.PrefixLength,
PrefixOption.ValidLifetime,
PrefixOption.PreferredLifetime,
NULL,
NULL
);
if (EFI_ERROR (Status)) {
goto Exit;
}
}
}
//
// Adds the prefix option to stateless prefix option list.
//
PrefixList = Ip6CreatePrefixListEntry (
IpSb,
FALSE,
PrefixOption.ValidLifetime,
PrefixOption.PreferredLifetime,
PrefixOption.PrefixLength,
&PrefixOption.Prefix
);
if (PrefixList == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
} else if (PrefixList != NULL) {
//
// Reset the preferred lifetime of the address if the advertised prefix exists.
// Perform specific action to valid lifetime together.
//
PrefixList->PreferredLifetime = PrefixOption.PreferredLifetime;
if ((PrefixOption.ValidLifetime > 7200) ||
(PrefixOption.ValidLifetime > PrefixList->ValidLifetime))
{
//
// If the received Valid Lifetime is greater than 2 hours or
// greater than RemainingLifetime, set the valid lifetime of the
// corresponding address to the advertised Valid Lifetime.
//
PrefixList->ValidLifetime = PrefixOption.ValidLifetime;
} else if (PrefixList->ValidLifetime <= 7200) {
//
// If RemainingLifetime is less than or equals to 2 hours, ignore the
// Prefix Information option with regards to the valid lifetime.
// TODO: If this option has been authenticated, set the valid lifetime.
//
} else {
//
// Otherwise, reset the valid lifetime of the corresponding
// address to 2 hours.
//
PrefixList->ValidLifetime = 7200;
}
}
}
Offset += sizeof (IP6_PREFIX_INFO_OPTION);
break;
case Ip6OptionMtu:
NetbufCopy (Packet, Offset, sizeof (IP6_MTU_OPTION), (UINT8 *)&MTUOption);
//
// Option size validity ensured by Ip6IsNDOptionValid().
//
ASSERT (MTUOption.Length == 1);
ASSERT (Offset + (UINT32)MTUOption.Length * 8 <= (UINT32)Head->PayloadLength);
//
// Use IPv6 minimum link MTU 1280 bytes as the maximum packet size in order
// to omit implementation of Path MTU Discovery. Thus ignore the MTU option
// in Router Advertisement.
//
Offset += sizeof (IP6_MTU_OPTION);
break;
default:
//
// Silently ignore unrecognized options
//
NetbufCopy (Packet, Offset + sizeof (UINT8), sizeof (UINT8), &Length);
ASSERT (Length != 0);
Offset += (UINT32)Length * 8;
break;
}
}
Status = EFI_SUCCESS;
Exit:
NetbufFree (Packet);
return Status;
}
/**
Process the ICMPv6 redirect message. Find the instance, then update
its route cache.
@param[in] IpSb The IP6 service binding instance that received
the packet.
@param[in] Head The IP head of the received ICMPv6 packet.
@param[in] Packet The content of the ICMPv6 redirect packet with
the IP head removed.
@retval EFI_INVALID_PARAMETER The parameter is invalid.
@retval EFI_OUT_OF_RESOURCES Insufficient resources to complete the
operation.
@retval EFI_SUCCESS Successfully updated the route caches.
**/
EFI_STATUS
Ip6ProcessRedirect (
IN IP6_SERVICE *IpSb,
IN EFI_IP6_HEADER *Head,
IN NET_BUF *Packet
)
{
IP6_ICMP_INFORMATION_HEAD *Icmp;
EFI_IPv6_ADDRESS *Target;
EFI_IPv6_ADDRESS *IcmpDest;
UINT8 *Option;
UINT16 OptionLen;
IP6_ROUTE_ENTRY *RouteEntry;
IP6_ROUTE_CACHE_ENTRY *RouteCache;
IP6_NEIGHBOR_ENTRY *NeighborCache;
INT32 Length;
UINT8 OptLen;
IP6_ETHER_ADDR_OPTION *LinkLayerOption;
EFI_MAC_ADDRESS Mac;
UINT32 Index;
BOOLEAN IsRouter;
EFI_STATUS Status;
INTN Result;
Status = EFI_INVALID_PARAMETER;
Icmp = (IP6_ICMP_INFORMATION_HEAD *)NetbufGetByte (Packet, 0, NULL);
if (Icmp == NULL) {
goto Exit;
}
//
// Validate the incoming Redirect message
//
//
// The IP Hop Limit field has a value of 255, i.e. the packet
// could not possibly have been forwarded by a router.
// ICMP Code is 0.
// ICMP length (derived from the IP length) is 40 or more octets.
//
if ((Head->HopLimit != IP6_HOP_LIMIT) || (Icmp->Head.Code != 0) ||
(Head->PayloadLength < IP6_REDITECT_LENGTH))
{
goto Exit;
}
//
// The IP source address must be a link-local address
//
if (!NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
goto Exit;
}
//
// The dest of this ICMP redirect message is not us.
//
if (!Ip6IsOneOfSetAddress (IpSb, &Head->DestinationAddress, NULL, NULL)) {
goto Exit;
}
//
// All included options have a length that is greater than zero.
//
OptionLen = (UINT16)(Head->PayloadLength - IP6_REDITECT_LENGTH);
if (OptionLen != 0) {
Option = NetbufGetByte (Packet, IP6_REDITECT_LENGTH, NULL);
ASSERT (Option != NULL);
if (!Ip6IsNDOptionValid (Option, OptionLen)) {
goto Exit;
}
}
Target = (EFI_IPv6_ADDRESS *)(Icmp + 1);
IcmpDest = Target + 1;
//
// The ICMP Destination Address field in the redirect message does not contain
// a multicast address.
//
if (IP6_IS_MULTICAST (IcmpDest)) {
goto Exit;
}
//
// The ICMP Target Address is either a link-local address (when redirected to
// a router) or the same as the ICMP Destination Address (when redirected to
// the on-link destination).
//
IsRouter = (BOOLEAN) !EFI_IP6_EQUAL (Target, IcmpDest);
if (!NetIp6IsLinkLocalAddr (Target) && IsRouter) {
goto Exit;
}
//
// Check the options. The only interested option here is the target-link layer
// address option.
//
Length = Packet->TotalSize - 40;
Option = (UINT8 *)(IcmpDest + 1);
LinkLayerOption = NULL;
while (Length > 0) {
switch (*Option) {
case Ip6OptionEtherTarget:
LinkLayerOption = (IP6_ETHER_ADDR_OPTION *)Option;
OptLen = LinkLayerOption->Length;
if (OptLen != 1) {
//
// For ethernet, the length must be 1.
//
goto Exit;
}
break;
default:
OptLen = *(Option + 1);
if (OptLen == 0) {
//
// A length of 0 is invalid.
//
goto Exit;
}
break;
}
Length -= 8 * OptLen;
Option += 8 * OptLen;
}
if (Length != 0) {
goto Exit;
}
//
// The IP source address of the Redirect is the same as the current
// first-hop router for the specified ICMP Destination Address.
//
RouteCache = Ip6FindRouteCache (IpSb->RouteTable, IcmpDest, &Head->DestinationAddress);
if (RouteCache != NULL) {
if (!EFI_IP6_EQUAL (&RouteCache->NextHop, &Head->SourceAddress)) {
//
// The source of this Redirect message must match the NextHop of the
// corresponding route cache entry.
//
goto Exit;
}
//
// Update the NextHop.
//
IP6_COPY_ADDRESS (&RouteCache->NextHop, Target);
if (!IsRouter) {
RouteEntry = (IP6_ROUTE_ENTRY *)RouteCache->Tag;
RouteEntry->Flag = RouteEntry->Flag | IP6_DIRECT_ROUTE;
}
} else {
//
// Get the Route Entry.
//
RouteEntry = Ip6FindRouteEntry (IpSb->RouteTable, IcmpDest, NULL);
if (RouteEntry == NULL) {
RouteEntry = Ip6CreateRouteEntry (IcmpDest, 0, NULL);
if (RouteEntry == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
}
if (!IsRouter) {
RouteEntry->Flag = IP6_DIRECT_ROUTE;
}
//
// Create a route cache for this.
//
RouteCache = Ip6CreateRouteCacheEntry (
IcmpDest,
&Head->DestinationAddress,
Target,
(UINTN)RouteEntry
);
if (RouteCache == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
//
// Insert the newly created route cache entry.
//
Index = IP6_ROUTE_CACHE_HASH (IcmpDest, &Head->DestinationAddress);
InsertHeadList (&IpSb->RouteTable->Cache.CacheBucket[Index], &RouteCache->Link);
}
//
// Try to locate the neighbor cache for the Target.
//
NeighborCache = Ip6FindNeighborEntry (IpSb, Target);
if (LinkLayerOption != NULL) {
if (NeighborCache == NULL) {
//
// Create a neighbor cache for the Target.
//
ZeroMem (&Mac, sizeof (EFI_MAC_ADDRESS));
CopyMem (&Mac, LinkLayerOption->EtherAddr, 6);
NeighborCache = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, Target, &Mac);
if (NeighborCache == NULL) {
//
// Just report a success here. The neighbor cache can be created in
// some other place.
//
Status = EFI_SUCCESS;
goto Exit;
}
NeighborCache->State = EfiNeighborStale;
NeighborCache->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
} else {
Result = CompareMem (LinkLayerOption->EtherAddr, &NeighborCache->LinkAddress, 6);
//
// If the link-local address is the same as that already in the cache,
// the cache entry's state remains unchanged. Otherwise update the
// reachability state to STALE.
//
if ((NeighborCache->State == EfiNeighborInComplete) || (Result != 0)) {
CopyMem (&NeighborCache->LinkAddress, LinkLayerOption->EtherAddr, 6);
NeighborCache->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
if (NeighborCache->State == EfiNeighborInComplete) {
//
// Send queued packets if exist.
//
NeighborCache->State = EfiNeighborStale;
NeighborCache->CallBack ((VOID *)NeighborCache);
} else {
NeighborCache->State = EfiNeighborStale;
}
}
}
}
if ((NeighborCache != NULL) && IsRouter) {
//
// The Target is a router, set IsRouter to TRUE.
//
NeighborCache->IsRouter = TRUE;
}
Status = EFI_SUCCESS;
Exit:
NetbufFree (Packet);
return Status;
}
/**
Add Neighbor cache entries. It is a work function for EfiIp6Neighbors().
@param[in] IpSb The IP6 service binding instance.
@param[in] TargetIp6Address Pointer to Target IPv6 address.
@param[in] TargetLinkAddress Pointer to link-layer address of the target. Ignored if NULL.
@param[in] Timeout Time in 100-ns units that this entry will remain in the neighbor
cache. It will be deleted after Timeout. A value of zero means that
the entry is permanent. A non-zero value means that the entry is
dynamic.
@param[in] Override If TRUE, the cached link-layer address of the matching entry will
be overridden and updated; if FALSE, and if a
corresponding cache entry already existed, EFI_ACCESS_DENIED
will be returned.
@retval EFI_SUCCESS The neighbor cache entry has been added.
@retval EFI_OUT_OF_RESOURCES Could not add the entry to the neighbor cache
due to insufficient resources.
@retval EFI_NOT_FOUND TargetLinkAddress is NULL.
@retval EFI_ACCESS_DENIED The to-be-added entry is already defined in the neighbor cache,
and that entry is tagged as un-overridden (when DeleteFlag
is FALSE).
**/
EFI_STATUS
Ip6AddNeighbor (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *TargetIp6Address,
IN EFI_MAC_ADDRESS *TargetLinkAddress OPTIONAL,
IN UINT32 Timeout,
IN BOOLEAN Override
)
{
IP6_NEIGHBOR_ENTRY *Neighbor;
Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
if (Neighbor != NULL) {
if (!Override) {
return EFI_ACCESS_DENIED;
} else {
if (TargetLinkAddress != NULL) {
IP6_COPY_LINK_ADDRESS (&Neighbor->LinkAddress, TargetLinkAddress);
}
}
} else {
if (TargetLinkAddress == NULL) {
return EFI_NOT_FOUND;
}
Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, TargetIp6Address, TargetLinkAddress);
if (Neighbor == NULL) {
return EFI_OUT_OF_RESOURCES;
}
}
Neighbor->State = EfiNeighborReachable;
if (Timeout != 0) {
Neighbor->Ticks = IP6_GET_TICKS (Timeout / TICKS_PER_MS);
Neighbor->Dynamic = TRUE;
} else {
Neighbor->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
}
return EFI_SUCCESS;
}
/**
Delete or update Neighbor cache entries. It is a work function for EfiIp6Neighbors().
@param[in] IpSb The IP6 service binding instance.
@param[in] TargetIp6Address Pointer to Target IPv6 address.
@param[in] TargetLinkAddress Pointer to link-layer address of the target. Ignored if NULL.
@param[in] Timeout Time in 100-ns units that this entry will remain in the neighbor
cache. It will be deleted after Timeout. A value of zero means that
the entry is permanent. A non-zero value means that the entry is
dynamic.
@param[in] Override If TRUE, the cached link-layer address of the matching entry will
be overridden and updated; if FALSE, and if a
corresponding cache entry already existed, EFI_ACCESS_DENIED
will be returned.
@retval EFI_SUCCESS The neighbor cache entry has been updated or deleted.
@retval EFI_NOT_FOUND This entry is not in the neighbor cache.
**/
EFI_STATUS
Ip6DelNeighbor (
IN IP6_SERVICE *IpSb,
IN EFI_IPv6_ADDRESS *TargetIp6Address,
IN EFI_MAC_ADDRESS *TargetLinkAddress OPTIONAL,
IN UINT32 Timeout,
IN BOOLEAN Override
)
{
IP6_NEIGHBOR_ENTRY *Neighbor;
Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
if (Neighbor == NULL) {
return EFI_NOT_FOUND;
}
RemoveEntryList (&Neighbor->Link);
FreePool (Neighbor);
return EFI_SUCCESS;
}
/**
The heartbeat timer of ND module in IP6_TIMER_INTERVAL_IN_MS milliseconds.
This time routine handles DAD module and neighbor state transition.
It is also responsible for sending out router solicitations.
@param[in] Event The IP6 service instance's heartbeat timer.
@param[in] Context The IP6 service instance.
**/
VOID
EFIAPI
Ip6NdFasterTimerTicking (
IN EFI_EVENT Event,
IN VOID *Context
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Next;
LIST_ENTRY *Entry2;
IP6_INTERFACE *IpIf;
IP6_DELAY_JOIN_LIST *DelayNode;
EFI_IPv6_ADDRESS Source;
IP6_DAD_ENTRY *DupAddrDetect;
EFI_STATUS Status;
IP6_NEIGHBOR_ENTRY *NeighborCache;
EFI_IPv6_ADDRESS Destination;
IP6_SERVICE *IpSb;
BOOLEAN Flag;
IpSb = (IP6_SERVICE *)Context;
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
ZeroMem (&Source, sizeof (EFI_IPv6_ADDRESS));
//
// A host SHOULD transmit up to MAX_RTR_SOLICITATIONS (3) Router
// Solicitation messages, each separated by at least
// RTR_SOLICITATION_INTERVAL (4) seconds.
//
if ((IpSb->Ip6ConfigInstance.Policy == Ip6ConfigPolicyAutomatic) &&
!IpSb->RouterAdvertiseReceived &&
(IpSb->SolicitTimer > 0)
)
{
if ((IpSb->Ticks == 0) || (--IpSb->Ticks == 0)) {
Status = Ip6SendRouterSolicit (IpSb, NULL, NULL, NULL, NULL);
if (!EFI_ERROR (Status)) {
IpSb->SolicitTimer--;
IpSb->Ticks = (UINT32)IP6_GET_TICKS (IP6_RTR_SOLICITATION_INTERVAL);
}
}
}
NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link);
//
// Process the delay list to join the solicited-node multicast address.
//
NET_LIST_FOR_EACH_SAFE (Entry2, Next, &IpIf->DelayJoinList) {
DelayNode = NET_LIST_USER_STRUCT (Entry2, IP6_DELAY_JOIN_LIST, Link);
if ((DelayNode->DelayTime == 0) || (--DelayNode->DelayTime == 0)) {
//
// The timer expires, init the duplicate address detection.
//
Ip6InitDADProcess (
DelayNode->Interface,
DelayNode->AddressInfo,
DelayNode->DadCallback,
DelayNode->Context
);
//
// Remove the delay node
//
RemoveEntryList (&DelayNode->Link);
FreePool (DelayNode);
}
}
//
// Process the duplicate address detection list.
//
NET_LIST_FOR_EACH_SAFE (Entry2, Next, &IpIf->DupAddrDetectList) {
DupAddrDetect = NET_LIST_USER_STRUCT (Entry2, IP6_DAD_ENTRY, Link);
if ((DupAddrDetect->RetransTick == 0) || (--DupAddrDetect->RetransTick == 0)) {
//
// The timer expires, check the remaining transmit counts.
//
if (DupAddrDetect->Transmit < DupAddrDetect->MaxTransmit) {
//
// Send the Neighbor Solicitation message with
// Source - unspecified address, destination - solicited-node multicast address
// Target - the address to be validated
//
Status = Ip6SendNeighborSolicit (
IpSb,
NULL,
&DupAddrDetect->Destination,
&DupAddrDetect->AddressInfo->Address,
NULL
);
if (EFI_ERROR (Status)) {
return;
}
DupAddrDetect->Transmit++;
DupAddrDetect->RetransTick = IP6_GET_TICKS (IpSb->RetransTimer);
} else {
//
// All required solicitation has been sent out, and the RetransTime after the last
// Neighbor Solicit is elapsed, finish the DAD process.
//
Flag = FALSE;
if ((DupAddrDetect->Receive == 0) ||
(DupAddrDetect->Transmit <= DupAddrDetect->Receive))
{
Flag = TRUE;
}
Ip6OnDADFinished (Flag, IpIf, DupAddrDetect);
}
}
}
}
//
// Polling the state of Neighbor cache
//
NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->NeighborTable) {
NeighborCache = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);
switch (NeighborCache->State) {
case EfiNeighborInComplete:
if (NeighborCache->Ticks > 0) {
--NeighborCache->Ticks;
}
//
// Retransmit Neighbor Solicitation messages approximately every
// RetransTimer milliseconds while awaiting a response.
//
if (NeighborCache->Ticks == 0) {
if (NeighborCache->Transmit > 1) {
//
// Send out multicast neighbor solicitation for address resolution.
// After last neighbor solicitation message has been sent out, wait
// for RetransTimer and then remove entry if no response is received.
//
Ip6CreateSNMulticastAddr (&NeighborCache->Neighbor, &Destination);
Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
if (EFI_ERROR (Status)) {
return;
}
Status = Ip6SendNeighborSolicit (
IpSb,
&Source,
&Destination,
&NeighborCache->Neighbor,
&IpSb->SnpMode.CurrentAddress
);
if (EFI_ERROR (Status)) {
return;
}
}
//
// Update the retransmit times.
//
if (NeighborCache->Transmit > 0) {
--NeighborCache->Transmit;
NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer);
}
}
if (NeighborCache->Transmit == 0) {
//
// Timeout, send ICMP destination unreachable packet and then remove entry
//
Status = Ip6FreeNeighborEntry (
IpSb,
NeighborCache,
TRUE,
TRUE,
EFI_ICMP_ERROR,
NULL,
NULL
);
if (EFI_ERROR (Status)) {
return;
}
}
break;
case EfiNeighborReachable:
//
// This entry is inserted by EfiIp6Neighbors() as static entry
// and will not timeout.
//
if (!NeighborCache->Dynamic && (NeighborCache->Ticks == IP6_INFINIT_LIFETIME)) {
break;
}
if ((NeighborCache->Ticks == 0) || (--NeighborCache->Ticks == 0)) {
if (NeighborCache->Dynamic) {
//
// This entry is inserted by EfiIp6Neighbors() as dynamic entry
// and will be deleted after timeout.
//
Status = Ip6FreeNeighborEntry (
IpSb,
NeighborCache,
FALSE,
TRUE,
EFI_TIMEOUT,
NULL,
NULL
);
if (EFI_ERROR (Status)) {
return;
}
} else {
NeighborCache->State = EfiNeighborStale;
NeighborCache->Ticks = (UINT32)IP6_INFINIT_LIFETIME;
}
}
break;
case EfiNeighborDelay:
if ((NeighborCache->Ticks == 0) || (--NeighborCache->Ticks == 0)) {
NeighborCache->State = EfiNeighborProbe;
NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer);
NeighborCache->Transmit = IP6_MAX_UNICAST_SOLICIT + 1;
//
// Send out unicast neighbor solicitation for Neighbor Unreachability Detection
//
Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
if (EFI_ERROR (Status)) {
return;
}
Status = Ip6SendNeighborSolicit (
IpSb,
&Source,
&NeighborCache->Neighbor,
&NeighborCache->Neighbor,
&IpSb->SnpMode.CurrentAddress
);
if (EFI_ERROR (Status)) {
return;
}
NeighborCache->Transmit--;
}
break;
case EfiNeighborProbe:
if (NeighborCache->Ticks > 0) {
--NeighborCache->Ticks;
}
//
// Retransmit Neighbor Solicitation messages approximately every
// RetransTimer milliseconds while awaiting a response.
//
if (NeighborCache->Ticks == 0) {
if (NeighborCache->Transmit > 1) {
//
// Send out unicast neighbor solicitation for Neighbor Unreachability
// Detection. After last neighbor solicitation message has been sent out,
// wait for RetransTimer and then remove entry if no response is received.
//
Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
if (EFI_ERROR (Status)) {
return;
}
Status = Ip6SendNeighborSolicit (
IpSb,
&Source,
&NeighborCache->Neighbor,
&NeighborCache->Neighbor,
&IpSb->SnpMode.CurrentAddress
);
if (EFI_ERROR (Status)) {
return;
}
}
//
// Update the retransmit times.
//
if (NeighborCache->Transmit > 0) {
--NeighborCache->Transmit;
NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer);
}
}
if (NeighborCache->Transmit == 0) {
//
// Delete the neighbor entry.
//
Status = Ip6FreeNeighborEntry (
IpSb,
NeighborCache,
FALSE,
TRUE,
EFI_TIMEOUT,
NULL,
NULL
);
if (EFI_ERROR (Status)) {
return;
}
}
break;
default:
break;
}
}
}
/**
The heartbeat timer of ND module in 1 second. This time routine handles following
things: 1) maintain default router list; 2) maintain prefix options;
3) maintain route caches.
@param[in] IpSb The IP6 service binding instance.
**/
VOID
Ip6NdTimerTicking (
IN IP6_SERVICE *IpSb
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *Next;
IP6_DEFAULT_ROUTER *DefaultRouter;
IP6_PREFIX_LIST_ENTRY *PrefixOption;
UINT8 Index;
IP6_ROUTE_CACHE_ENTRY *RouteCache;
//
// Decrease the lifetime of default router, if expires remove it from default router list.
//
NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->DefaultRouterList) {
DefaultRouter = NET_LIST_USER_STRUCT (Entry, IP6_DEFAULT_ROUTER, Link);
if (DefaultRouter->Lifetime != IP6_INF_ROUTER_LIFETIME) {
if ((DefaultRouter->Lifetime == 0) || (--DefaultRouter->Lifetime == 0)) {
Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
}
}
}
//
// Decrease Valid lifetime and Preferred lifetime of Prefix options and corresponding addresses.
//
NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->AutonomousPrefix) {
PrefixOption = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
if (PrefixOption->ValidLifetime != (UINT32)IP6_INFINIT_LIFETIME) {
if ((PrefixOption->ValidLifetime > 0) && (--PrefixOption->ValidLifetime > 0)) {
if ((PrefixOption->PreferredLifetime != (UINT32)IP6_INFINIT_LIFETIME) &&
(PrefixOption->PreferredLifetime > 0)
)
{
--PrefixOption->PreferredLifetime;
}
} else {
Ip6DestroyPrefixListEntry (IpSb, PrefixOption, FALSE, TRUE);
}
}
}
NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->OnlinkPrefix) {
PrefixOption = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
if (PrefixOption->ValidLifetime != (UINT32)IP6_INFINIT_LIFETIME) {
if ((PrefixOption->ValidLifetime == 0) || (--PrefixOption->ValidLifetime == 0)) {
Ip6DestroyPrefixListEntry (IpSb, PrefixOption, TRUE, TRUE);
}
}
}
//
// Each bucket of route cache can contain at most IP6_ROUTE_CACHE_MAX entries.
// Remove the entries at the tail of the bucket. These entries
// are likely to be used least.
// Reclaim frequency is set to 1 second.
//
for (Index = 0; Index < IP6_ROUTE_CACHE_HASH_SIZE; Index++) {
while (IpSb->RouteTable->Cache.CacheNum[Index] > IP6_ROUTE_CACHE_MAX) {
Entry = NetListRemoveTail (&IpSb->RouteTable->Cache.CacheBucket[Index]);
if (Entry == NULL) {
break;
}
RouteCache = NET_LIST_USER_STRUCT (Entry, IP6_ROUTE_CACHE_ENTRY, Link);
Ip6FreeRouteCacheEntry (RouteCache);
ASSERT (IpSb->RouteTable->Cache.CacheNum[Index] > 0);
IpSb->RouteTable->Cache.CacheNum[Index]--;
}
}
}