summaryrefslogtreecommitdiff
path: root/NetworkPkg/Ip6Dxe/Ip6Nd.c
diff options
context:
space:
mode:
authorhhtian <hhtian@6f19259b-4bc3-4df7-8a09-765794883524>2010-11-01 06:13:54 +0000
committerhhtian <hhtian@6f19259b-4bc3-4df7-8a09-765794883524>2010-11-01 06:13:54 +0000
commita3bcde70e6dc69000f85cc5deee98101d2ae200a (patch)
tree693709a5293f80b320931693b34b2d6983cfcf4b /NetworkPkg/Ip6Dxe/Ip6Nd.c
parent12873d57666d0beff41959a1fb8f9062016f0983 (diff)
downloadedk2-platforms-a3bcde70e6dc69000f85cc5deee98101d2ae200a.tar.xz
Add NetworkPkg (P.UDK2010.UP3.Network.P1)
git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@10986 6f19259b-4bc3-4df7-8a09-765794883524
Diffstat (limited to 'NetworkPkg/Ip6Dxe/Ip6Nd.c')
-rw-r--r--NetworkPkg/Ip6Dxe/Ip6Nd.c3141
1 files changed, 3141 insertions, 0 deletions
diff --git a/NetworkPkg/Ip6Dxe/Ip6Nd.c b/NetworkPkg/Ip6Dxe/Ip6Nd.c
new file mode 100644
index 0000000000..f2a47a8073
--- /dev/null
+++ b/NetworkPkg/Ip6Dxe/Ip6Nd.c
@@ -0,0 +1,3141 @@
+/** @file
+ Implementation of Neighbor Discovery support routines.
+
+ Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>
+
+ This program and the accompanying materials
+ are licensed and made available under the terms and conditions of the BSD License
+ which accompanies this distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php.
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include "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_NUM) {
+ 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;
+}
+
+/**
+ Destory 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.
+ @param[in] DefaultRouter The to be destroyed IP6_DEFAULT_ROUTER.
+
+**/
+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;
+
+ 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 (IP6_CONFIG_DHCP6_OPTION_ORO);
+ Oro->OpLen = HTONS (2);
+ *((UINT16 *) &Oro->Data[0]) = HTONS (IP6_CONFIG_DHCP6_OPTION_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);
+ //
+ // 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);
+
+ 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);
+ Option = NetbufGetByte (Packet, IP6_ND_LENGTH, 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) {
+ if (DupAddrDetect->Transmit == 0) {
+ //
+ // The NS is from another node to performing DAD on the same address since
+ // we haven't send out any NS yet. Fail DAD for the tentative address.
+ //
+ Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
+ Status = EFI_ICMP_ERROR;
+ goto Exit;
+ }
+
+ //
+ // 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 unspeicifed, 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);
+ Option = NetbufGetByte (Packet, IP6_ND_LENGTH, 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;
+ UINT16 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);
+ Option = NetbufGetByte (Packet, IP6_RA_LENGTH, 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 advertisment 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.
+ //
+ Offset = 16;
+ while (Offset < 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);
+ if (LinkLayerOption.Length <= 0) {
+ goto Exit;
+ }
+
+ 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 = (UINT16) (Offset + (UINT16) LinkLayerOption.Length * 8);
+ break;
+ case Ip6OptionPrefixInfo:
+ NetbufCopy (Packet, Offset, sizeof (IP6_PREFIX_INFO_OPTION), (UINT8 *) &PrefixOption);
+ if (PrefixOption.Length != 4) {
+ goto Exit;
+ }
+ 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 uniqeness 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 equls 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);
+ if (MTUOption.Length != 1) {
+ goto Exit;
+ }
+
+ //
+ // 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);
+ if (Length <= 0) {
+ goto Exit;
+ }
+
+ Offset = (UINT16) (Offset + (UINT16) 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 Insuffcient 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);
+ Option = NetbufGetByte (Packet, IP6_REDITECT_LENGTH, 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) maitain 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]--;
+ }
+ }
+}
+