Просмотр исходного кода

Add support for NDP's Duplicate Address Detection.

This change adds duplicate address detection, which is all that is needed
to declare a link-local IPv6 address unique. DAD requires NDP's neighbor
solicitation and advertisment messages. It sends solicitations and if no
advertisements are received within the default timeout, an address is
unique. If an advertisement is received, then the address is not unique
and the system must not use it.
Chris Stevens 6 лет назад
Родитель
Сommit
3ef7e28e81

+ 1 - 0
drivers/net/netcore/Makefile

@@ -49,6 +49,7 @@ OBJS = addr.o            \
        ipv6/icmp6.o      \
        ipv6/ip6.o        \
        ipv6/mld.o        \
+       ipv6/ndp.o        \
        netlink/netlink.o \
        netlink/genctrl.o \
        netlink/generic.o \

+ 9 - 4
drivers/net/netcore/addr.c

@@ -3053,13 +3053,18 @@ Return Value:
         //
 
         if ((Information->Flags & NETWORK_DEVICE_FLAG_CONFIGURED) != 0) {
-            if ((Information->Address.Domain != Domain) ||
-                (Information->Subnet.Domain != Domain) ||
-                (Information->Gateway.Domain != Domain) ||
+            if (((Information->Address.Domain != Domain) &&
+                 (Information->Address.Domain != NetDomainInvalid)) ||
+                ((Information->Subnet.Domain != Domain) &&
+                 (Information->Subnet.Domain != NetDomainInvalid)) ||
+                ((Information->Gateway.Domain != Domain) &&
+                 (Information->Gateway.Domain != NetDomainInvalid)) ||
                 ((Information->ConfigurationMethod !=
                   NetworkAddressConfigurationStatic) &&
                  (Information->ConfigurationMethod !=
-                  NetworkAddressConfigurationDhcp))) {
+                  NetworkAddressConfigurationDhcp) &&
+                 (Information->ConfigurationMethod !=
+                  NetworkAddressConfigurationStateless))) {
 
                 Status = STATUS_INVALID_CONFIGURATION;
                 goto GetSetNetworkDeviceInformationEnd;

+ 1 - 0
drivers/net/netcore/build.ck

@@ -46,6 +46,7 @@ function build() {
         "ipv6/icmp6.c",
         "ipv6/ip6.c",
         "ipv6/mld.c",
+        "ipv6/ndp.c",
         "mcast.c",
         "netcore.c",
         "netlink/netlink.c",

+ 1 - 10
drivers/net/netcore/ipv4/igmp.c

@@ -3154,11 +3154,8 @@ Return Value:
     PIGMP_LINK NewIgmpLink;
     IGMP_LINK SearchLink;
     KSTATUS Status;
-    BOOL TreeLockHeld;
 
     IgmpLink = NULL;
-    NewIgmpLink = NULL;
-    TreeLockHeld = FALSE;
     NewIgmpLink = MmAllocatePagedPool(sizeof(IGMP_LINK), IGMP_ALLOCATION_TAG);
     if (NewIgmpLink == NULL) {
         Status = STATUS_INSUFFICIENT_RESOURCES;
@@ -3233,7 +3230,6 @@ Return Value:
 
     SearchLink.Link = Link;
     KeAcquireSharedExclusiveLockExclusive(NetIgmpLinkLock);
-    TreeLockHeld = TRUE;
     FoundNode = RtlRedBlackTreeSearch(&NetIgmpLinkTree, &(SearchLink.Node));
     if (FoundNode == NULL) {
         RtlRedBlackTreeInsert(&NetIgmpLinkTree, &(NewIgmpLink->Node));
@@ -3246,13 +3242,8 @@ Return Value:
 
     NetpIgmpLinkAddReference(IgmpLink);
     KeReleaseSharedExclusiveLockExclusive(NetIgmpLinkLock);
-    TreeLockHeld = FALSE;
 
 CreateOrLookupLinkEnd:
-    if (TreeLockHeld != FALSE) {
-        KeReleaseSharedExclusiveLockExclusive(NetIgmpLinkLock);
-    }
-
     if (NewIgmpLink != NULL) {
         NetpIgmpLinkReleaseReference(NewIgmpLink);
     }
@@ -3479,7 +3470,7 @@ Return Value:
     PIGMP_LINK SecondIgmpLink;
 
     FirstIgmpLink = RED_BLACK_TREE_VALUE(FirstNode, IGMP_LINK, Node);
-    SecondIgmpLink = RED_BLACK_TREE_VALUE(FirstNode, IGMP_LINK, Node);
+    SecondIgmpLink = RED_BLACK_TREE_VALUE(SecondNode, IGMP_LINK, Node);
     if (FirstIgmpLink->Link == SecondIgmpLink->Link) {
         return ComparisonResultSame;
 

+ 3 - 0
drivers/net/netcore/ipv4/ip4.c

@@ -1638,6 +1638,9 @@ Return Value:
 
     if (Header->TimeToLive == IP4_LINK_LOCAL_TIME_TO_LIVE) {
         Packet->Flags |= NET_PACKET_FLAG_LINK_LOCAL_HOP_LIMIT;
+
+    } else if (Header->TimeToLive == IP4_MAX_TIME_TO_LIVE) {
+        Packet->Flags |= NET_PACKET_FLAG_MAX_HOP_LIMIT;
     }
 
     //

+ 39 - 0
drivers/net/netcore/ipv6/icmp6.c

@@ -40,6 +40,15 @@ Environment:
 #include <minoca/net/ip6.h>
 #include <minoca/net/icmp6.h>
 #include "mld.h"
+#include "ndp.h"
+
+//
+// While ICMPv6 is built into netcore and the same binary as IPv6, share the
+// well-known addresses. This could easily be changed if the binaries need to
+// be separated out.
+//
+
+#include "ip6addr.h"
 
 //
 // ---------------------------------------------------------------- Definitions
@@ -224,6 +233,7 @@ Return Value:
     //
 
     NetpMldInitialize();
+    NetpNdpInitialize();
     return;
 }
 
@@ -601,6 +611,14 @@ Return Value:
         NetpMldProcessReceivedData(ReceiveContext);
         break;
 
+    case ICMP6_MESSAGE_TYPE_NDP_ROUTER_SOLICITATION:
+    case ICMP6_MESSAGE_TYPE_NDP_ROUTER_ADVERTISEMENT:
+    case ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_SOLICITATION:
+    case ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_ADVERTISEMENT:
+    case ICMP6_MESSAGE_TYPE_NDP_REDIRECT:
+        NetpNdpProcessReceivedData(ReceiveContext);
+        break;
+
     default:
         break;
     }
@@ -739,6 +757,7 @@ Return Value:
 
 {
 
+    PICMP6_ADDRESS_CONFIGURATION_REQUEST ConfigurationRequest;
     SOCKET_ICMP6_OPTION Icmp6;
     PIP6_ADDRESS MulticastAddress;
     PNET_NETWORK_MULTICAST_REQUEST MulticastRequest;
@@ -788,6 +807,26 @@ Return Value:
 
         break;
 
+    case SocketIcmp6OptionConfigureAddress:
+        if (Set == FALSE) {
+            Status = STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+            break;
+        }
+
+        RequiredSize = sizeof(ICMP6_ADDRESS_CONFIGURATION_REQUEST);
+        if (*DataSize < RequiredSize) {
+            *DataSize = RequiredSize;
+            Status = STATUS_BUFFER_TOO_SMALL;
+            break;
+        }
+
+        ConfigurationRequest = (PICMP6_ADDRESS_CONFIGURATION_REQUEST)Data;
+        Status = NetpNdpConfigureAddress(ConfigurationRequest->Link,
+                                         ConfigurationRequest->LinkAddress,
+                                         ConfigurationRequest->Configure);
+
+        break;
+
     default:
         Status = STATUS_NOT_SUPPORTED_BY_PROTOCOL;
         break;

+ 59 - 23
drivers/net/netcore/ipv6/ip6.c

@@ -251,6 +251,11 @@ const UCHAR NetIp6AllMld2RoutersMulticastAddress[IP6_ADDRESS_SIZE] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16
 };
 
+const UCHAR NetIp6SolicitedNodeMulticastPrefix[IP6_ADDRESS_SIZE] = {
+    0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00
+};
+
 //
 // ------------------------------------------------------------------ Functions
 //
@@ -1284,11 +1289,14 @@ Return Value:
     }
 
     //
-    // Record if the packet has a link-local hop limit.
+    // Record if the packet has a link-local or maximum hop limit.
     //
 
     if (Header->HopLimit == IP6_LINK_LOCAL_HOP_LIMIT) {
         Packet->Flags |= NET_PACKET_FLAG_LINK_LOCAL_HOP_LIMIT;
+
+    } else if (Header->HopLimit == IP6_MAX_HOP_LIMIT) {
+        Packet->Flags |= NET_PACKET_FLAG_MAX_HOP_LIMIT;
     }
 
     //
@@ -1374,6 +1382,7 @@ Return Value:
     LONG CurrentRun;
     LONG CurrentRunSize;
     PIP6_ADDRESS Ip6Address;
+    ULONG Length;
     UINTN RemainingSize;
     PSTR String;
     UINTN StringSize;
@@ -1493,23 +1502,26 @@ Return Value:
 
             StringSize = (UINTN)String - (UINTN)WorkingString;
             RemainingSize = IP6_MAX_ADDRESS_STRING_SIZE - StringSize;
-            String += RtlPrintToString(String,
-                                       RemainingSize,
-                                       CharacterEncodingDefault,
-                                       "%d.%d.%d.%d",
-                                       BytePointer[12],
-                                       BytePointer[13],
-                                       BytePointer[14],
-                                       BytePointer[15]);
-
+            Length = RtlPrintToString(String,
+                                      RemainingSize,
+                                      CharacterEncodingDefault,
+                                      "%d.%d.%d.%d",
+                                      BytePointer[12],
+                                      BytePointer[13],
+                                      BytePointer[14],
+                                      BytePointer[15]);
+
+            String += (Length - 1);
             break;
         }
 
-        String += RtlPrintToString(String,
-                                   5,
-                                   CharacterEncodingDefault,
-                                   "%x",
-                                   Words[WordIndex]);
+        Length = RtlPrintToString(String,
+                                  5,
+                                  CharacterEncodingDefault,
+                                  "%x",
+                                  Words[WordIndex]);
+
+        String += (Length - 1);
     }
 
     //
@@ -1528,11 +1540,13 @@ Return Value:
         String += 1;
         StringSize = (UINTN)String - (UINTN)WorkingString;
         RemainingSize = IP6_MAX_ADDRESS_STRING_SIZE - StringSize;
-        String += RtlPrintToString(String,
-                                   RemainingSize,
-                                   CharacterEncodingDefault,
-                                   "%d",
-                                   Ip6Address->Port);
+        Length = RtlPrintToString(String,
+                                  RemainingSize,
+                                  CharacterEncodingDefault,
+                                  "%d",
+                                  Ip6Address->Port);
+
+        String += (Length - 1);
     }
 
     //
@@ -2036,13 +2050,35 @@ Return Value:
 
 {
 
+    UINTN Option;
+    PNET_PROTOCOL_ENTRY Protocol;
+    ICMP6_ADDRESS_CONFIGURATION_REQUEST Request;
+    UINTN RequestSize;
+    KSTATUS Status;
+
     //
-    // TODO: Implement NDP and DHCPv6.
+    // ICMPv6 handles address configuration, hand off to the protocol.
     //
 
-    ASSERT(FALSE);
+    Protocol = NetGetProtocolEntry(SOCKET_INTERNET_PROTOCOL_ICMP6);
+    if (Protocol == NULL) {
+        return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+    }
 
-    return STATUS_NOT_IMPLEMENTED;
+    RequestSize = sizeof(ICMP6_ADDRESS_CONFIGURATION_REQUEST);
+    RtlZeroMemory(&Request, RequestSize);
+    Request.Link = Link;
+    Request.LinkAddress = LinkAddress;
+    Request.Configure = Configure;
+    Option = SocketIcmp6OptionConfigureAddress;
+    Status = Protocol->Interface.GetSetInformation(NULL,
+                                                   SocketInformationIcmp6,
+                                                   Option,
+                                                   &Request,
+                                                   &RequestSize,
+                                                   TRUE);
+
+    return Status;
 }
 
 KSTATUS

+ 1 - 0
drivers/net/netcore/ipv6/ip6addr.h

@@ -44,6 +44,7 @@ Author:
 extern const UCHAR NetIp6AllNodesMulticastAddress[IP6_ADDRESS_SIZE];
 extern const UCHAR NetIp6AllRoutersMulticastAddress[IP6_ADDRESS_SIZE];
 extern const UCHAR NetIp6AllMld2RoutersMulticastAddress[IP6_ADDRESS_SIZE];
+extern const UCHAR NetIp6SolicitedNodeMulticastPrefix[IP6_ADDRESS_SIZE];
 
 //
 // -------------------------------------------------------- Function Prototypes

+ 1 - 10
drivers/net/netcore/ipv6/mld.c

@@ -2448,11 +2448,8 @@ Return Value:
     PMLD_LINK NewMldLink;
     MLD_LINK SearchLink;
     KSTATUS Status;
-    BOOL TreeLockHeld;
 
     MldLink = NULL;
-    NewMldLink = NULL;
-    TreeLockHeld = FALSE;
     NewMldLink = MmAllocatePagedPool(sizeof(MLD_LINK), MLD_ALLOCATION_TAG);
     if (NewMldLink == NULL) {
         Status = STATUS_INSUFFICIENT_RESOURCES;
@@ -2527,7 +2524,6 @@ Return Value:
 
     SearchLink.Link = Link;
     KeAcquireSharedExclusiveLockExclusive(NetMldLinkLock);
-    TreeLockHeld = TRUE;
     FoundNode = RtlRedBlackTreeSearch(&NetMldLinkTree, &(SearchLink.Node));
     if (FoundNode == NULL) {
         RtlRedBlackTreeInsert(&NetMldLinkTree, &(NewMldLink->Node));
@@ -2540,13 +2536,8 @@ Return Value:
 
     NetpMldLinkAddReference(MldLink);
     KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
-    TreeLockHeld = FALSE;
 
 CreateOrLookupLinkEnd:
-    if (TreeLockHeld != FALSE) {
-        KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
-    }
-
     if (NewMldLink != NULL) {
         NetpMldLinkReleaseReference(NewMldLink);
     }
@@ -2773,7 +2764,7 @@ Return Value:
     PMLD_LINK SecondMldLink;
 
     FirstMldLink = RED_BLACK_TREE_VALUE(FirstNode, MLD_LINK, Node);
-    SecondMldLink = RED_BLACK_TREE_VALUE(FirstNode, MLD_LINK, Node);
+    SecondMldLink = RED_BLACK_TREE_VALUE(SecondNode, MLD_LINK, Node);
     if (FirstMldLink->Link == SecondMldLink->Link) {
         return ComparisonResultSame;
 

+ 2810 - 0
drivers/net/netcore/ipv6/ndp.c

@@ -0,0 +1,2810 @@
+/*++
+
+Copyright (c) 2017 Minoca Corp. All Rights Reserved
+
+Module Name:
+
+    ndp.c
+
+Abstract:
+
+    This module implements support for the Neighbor Discovery Protocol, which
+    translates network layer addresses (such as IP addresses) to physical
+    addresses (such as MAC addresses) and allows a node to find routers and its
+    neighbors.
+
+Author:
+
+    Chris Stevens 7-Sept-2017
+
+Environment:
+
+    Kernel
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// Network layer drivers are supposed to be able to stand on their own (ie be
+// able to be implemented outside the core net library). For the builtin once,
+// avoid including netcore.h, but still redefine those functions that would
+// otherwise generate imports.
+//
+
+#define NET_API __DLLEXPORT
+
+#include <minoca/kernel/driver.h>
+#include <minoca/net/netdrv.h>
+#include <minoca/net/ip6.h>
+#include <minoca/net/icmp6.h>
+
+//
+// While ICMPv6 and NDP are built into netcore and the same binary as IPv6,
+// share the well-known addresses. This could easily be changed if the binaries
+// need to be separated out.
+//
+
+#include "ip6addr.h"
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+#define NDP_ALLOCATION_TAG 0x2170644E // '!pdN'
+
+//
+// Define the set of router advertisement flags.
+//
+
+#define NDP_ROUTER_FLAG_MANAGED_ADDRESS_CONFIGURATION 0x01
+#define NDP_ROUTER_FLAG_OTHER_CONFIGURATION           0x02
+
+//
+// Define the set of neighbor advertisement flags.
+//
+
+#define NDP_NEIGHBOR_FLAG_ROUTER    0x01
+#define NDP_NEIGHBOR_FLAG_SOLICITED 0x02
+#define NDP_NEIGHBOR_FLAG_OVERRIDE  0x04
+
+//
+// Define the neighbor discovery message option types.
+//
+
+#define NDP_OPTION_TYPE_SOURCE_LINK_ADDRESS   0x01
+#define NDP_OPTION_TYPE_TARGET_LINK_ADDRESS   0x02
+#define NDP_OPTION_TYPE_PREFIX_INFORMATION    0x03
+#define NDP_OPTION_TYPE_REDIRECTED_HEADER     0x04
+#define NDP_OPTION_TYPE_MAX_TRANSMISSION_UNIT 0x05
+
+//
+// Define the bytes per unit of length for the NDP options.
+//
+
+#define NDP_OPTION_LENGTH_MULTIPLE 8
+
+//
+// Define the set of prefix information option flags.
+//
+
+#define NDP_PREFIX_FLAG_ON_LINK                          0x01
+#define NDP_PREFIX_FLAG_AUTONOMOUS_ADDRESS_CONFIGURATION 0x02
+
+//
+// All NDP packets should go out with an IPv6 hop limit of 255.
+//
+
+#define NDP_IP6_HOP_LIMIT 255
+
+//
+// Define the maximum amount of time to delay a solicitation, in milliseconds.
+//
+
+#define NDP_SOLICITATION_DELAY_MAX 1000
+
+//
+// Define the default retransmit timer, in milliseconds.
+//
+
+#define NDP_DEFAULT_RETRANMIT_TIMEOUT 1000
+
+//
+// Define the default number of duplicate address detection transmits.
+// RFC 4862 specifies the default as 1.
+//
+
+#define NDP_DEFAULT_DUPLICATE_ADDRESS_DETECTION_TRANSMIT_COUNT 1
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+typedef enum _NDP_NEIGHBOR_STATE {
+    NdpNeighborInvalid,
+    NdpNeighborTentative,
+    NdpNeighborDuplicate,
+} NDP_NEIGHBOR_STATE, *PNDP_NEIGHBOR_STATE;
+
+/*++
+
+Structure Description:
+
+    This structure defines a router solicitation message. The message options
+    immediately follow this structure.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    Reserved - This field is reserved. Must be set to zero.
+
+--*/
+
+typedef struct _NDP_ROUTER_SOLICITATION {
+    ICMP6_HEADER Header;
+    ULONG Reserved;
+} PACKED NDP_ROUTER_SOLICITATION, *PNDP_ROUTER_SOLICITATION;
+
+/*++
+
+Structure Description:
+
+    This structure defines a router advertisement message. The message options
+    immediately follows this structure.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    CurrentHopLimit - Stores the default value for the IPv6 hop limit that
+        should be used for outgoing packets.
+
+    Flags - Stores a bitmask of flags. See NDP_ROUTER_FLAG_* for definitions.
+
+    RouterLifetime - Stores the lifetime of the router, in seconds.
+
+    ReachableTime - Stores the time, in milliseconds, for which a node should
+        assume a neighbor is reachable after receiving a reachability
+        confirmation.
+
+    RetransmitTimer - Stores the time, in milliseconds, to be waited between
+        retransmitting neighbor solicitation messages.
+
+--*/
+
+typedef struct _NDP_ROUTER_ADVERTISEMENT {
+    ICMP6_HEADER Header;
+    UCHAR CurrentHopLimit;
+    UCHAR Flags;
+    USHORT RouterLifetime;
+    ULONG ReachableTime;
+    ULONG RetransmitTimer;
+} PACKED NDP_ROUTER_ADVERTISEMENT, *PNDP_ROUTER_ADVERTISEMENT;
+
+/*++
+
+Structure Description:
+
+    This structure defines a neighbor solicitation message. The message options
+    immediately follow this structure.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    Reserved - This field is reserved.
+
+    TargetAddress - Stores the IPv6 address of the target that is being
+        solicited. This cannot be a multicast address.
+
+--*/
+
+typedef struct _NDP_NEIGHBOR_SOLICITATION {
+    ICMP6_HEADER Header;
+    ULONG Reserved;
+    ULONG TargetAddress[IP6_ADDRESS_SIZE / sizeof(ULONG)];
+} PACKED NDP_NEIGHBOR_SOLICITATION, *PNDP_NEIGHBOR_SOLICITATION;
+
+/*++
+
+Structure Description:
+
+    This structure defines a neighbor advertisement message. The message
+    options immediately follow this structure.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    Flags - Store a bitmask of neighbor advertisement flags. See
+        NDP_NEIGHBOR_FLAG_* for definitions.
+
+    TargetAddress - Stores the IPv6 address of the node whose link-layer
+        address follows in the options. For solicited advertisements, this is
+        the IPv6 address sent in the neighbor solicitation. For unsolicited
+        advertisements, this is the IPv6 address of the node whose link-layer
+        address has changed.
+
+--*/
+
+typedef struct _NDP_NEIGHBOR_ADVERTISEMENT {
+    ICMP6_HEADER Header;
+    ULONG Flags;
+    ULONG TargetAddress[IP6_ADDRESS_SIZE / sizeof(ULONG)];
+} PACKED NDP_NEIGHBOR_ADVERTISEMENT, *PNDP_NEIGHBOR_ADVERTISEMENT;
+
+/*++
+
+Structure Description:
+
+    This structure defines an NDP redirect message. The message options
+    immediately follow this structure.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    Reserved - This field is reserved.
+
+    TargetAddress - Stores the IPv6 address that a better first hop to use when
+        comminicating with the destination address.
+
+    DestinationAddress - Stores the IPv6 address for which communication should
+        be redirected to the target address.
+
+--*/
+
+typedef struct _NDP_REDIRECT {
+    ICMP6_HEADER Header;
+    ULONG Reserved;
+    ULONG TargetAddress[IP6_ADDRESS_SIZE / sizeof(ULONG)];
+    ULONG DestinationAddress[IP6_ADDRESS_SIZE / sizeof(ULONG)];
+} PACKED NDP_REDIRECT, *PNDP_REDIRECT;
+
+/*++
+
+Structure Description:
+
+    This structure defines the header for an NDP option. The option data
+    immediately follows this structure.
+
+Members:
+
+    Type - Stores the NDP option type.
+
+    Length - Stores the length of the option, including the option header, in
+        8-byte units.
+
+--*/
+
+typedef struct _NDP_OPTION {
+    UCHAR Type;
+    UCHAR Length;
+} PACKED NDP_OPTION, *PNDP_OPTION;
+
+/*++
+
+Structure Description:
+
+    This structure defines the NDP prefix information option. This should
+    appear in router advertisement messages and be ignored for other messages.
+
+Members:
+
+    Header - Stores the NDP option header.
+
+    PrefixLength - Stores the number of leading bits in the prefix that are
+        valid. Values can range from 0 to 128.
+
+    Flags - Stores a bitmask of prefix information flags. See NDP_PREFIX_FLAG_*
+        for definitions.
+
+    ValidLifetime - Stores the length of time, in seconds, for which the prefix
+        is valid for on-link determination. If all bits are set, then there
+        is no timeout on the validity.
+
+    PreferredLifetime - Stores the length fo time, in seconds, for which
+        addresses generated using the prefix via stateless address
+        autoconfiguration (SLAAC) remain preferred.
+
+    Reserved - This field is reserved.
+
+    Prefix - Stores an IPv6 address or the prefix of an IPv6 address, the
+        length of which is specified by the prefix length field.
+
+--*/
+
+typedef struct _NDP_OPTION_PREFIX_INFORMATION {
+    NDP_OPTION Header;
+    UCHAR PrefixLength;
+    UCHAR Flags;
+    ULONG ValidLifetime;
+    ULONG PreferredLifetime;
+    ULONG Reserved;
+    ULONG Prefix[IP6_ADDRESS_SIZE / sizeof(ULONG)];
+} PACKED NDP_OPTION_PREFIX_INFORMATION, *PNDP_OPTION_PREFIX_INFORMATION;
+
+/*++
+
+Structure Description:
+
+    This structure defines the redirect header option. The IP header and data
+    immediately follow this structure.
+
+Members:
+
+    Header - Stores the NDP option header.
+
+    Reserved1 - This field is reserved.
+
+    Reserved2 - This field is reserved.
+
+--*/
+
+typedef struct _NDP_OPTION_REDIRECT_HEADER {
+    NDP_OPTION Header;
+    USHORT Reserved1;
+    ULONG Reserved2;
+} PACKED NDP_OPTION_REDIRECT_HEADER, *PNDP_OPTION_REDIRECT_HEADER;
+
+/*++
+
+Structure Description:
+
+    This structure defines the NDP maximum transmission unit option.
+
+Members:
+
+    Header - Stores the NDP option header.
+
+    Reserved - This field is reserved.
+
+    MaxTransmissionUnit - Stores the maximum transmission unit for the network,
+        in bytes.
+
+--*/
+
+typedef struct _NDP_OPTION_MTU {
+    NDP_OPTION Header;
+    USHORT Reserved;
+    ULONG MaxTransmissionUnit;
+} PACKED NDP_OPTION_MTU, *PNDP_OPTION_MTU;
+
+/*++
+
+Structure Description:
+
+    This structure defines per-link NDP information.
+
+Members:
+
+    Node - Stores the link's entry into the global tree of NDP links.
+
+    ReferenceCount - Stores the reference count on the structure.
+
+    Link - Stores a pointer to the network link to which this NDP link is
+        bound.
+
+    SharedExclusiveLock - Stores a pointer to a shared exclusive lock that
+        protects access to the neighbor cache.
+
+    NeighborCacheEvent - Stores a pointer to an event that is pulsed when a
+        change to the neighbor cache occurs.
+
+    NeighborCacheTree - Stores a pointer to the red-black tree of neighbor
+        cache entries.
+
+--*/
+
+typedef struct _NDP_LINK {
+    RED_BLACK_TREE_NODE Node;
+    volatile ULONG ReferenceCount;
+    PNET_LINK Link;
+    PSHARED_EXCLUSIVE_LOCK SharedExclusiveLock;
+    PKEVENT NeighborCacheEvent;
+    RED_BLACK_TREE NeighborCacheTree;
+} NDP_LINK, *PNDP_LINK;
+
+/*++
+
+Structure Description:
+
+    This structure defines a NDP neighbor cache entry.
+
+Members:
+
+    Node - Stores the entry into the link's neighbor cache tree.
+
+    State - Stores the current state of the neighbor cache entry.
+
+    NetworkAddress - Stores the network layer address of the neighbor. This is
+        the search key into the tree.
+
+    PhysicalAddress - Stores the link-layer address of the neighbor.
+
+--*/
+
+typedef struct _NDP_NEIGHBOR_ENTRY {
+    RED_BLACK_TREE_NODE Node;
+    NDP_NEIGHBOR_STATE State;
+    NETWORK_ADDRESS NetworkAddress;
+    NETWORK_ADDRESS PhysicalAddress;
+} NDP_NEIGHBOR_ENTRY, *PNDP_NEIGHBOR_ENTRY;
+
+/*++
+
+Structure Description:
+
+    This structure defines the NDP thread context used for address
+    configuration.
+
+Members:
+
+    NdpLink - Stores a pointer to the NDP link to work on.
+
+    LinkAddress - Stores a pointer to the link address entry to configure an
+        address for.
+
+--*/
+
+typedef struct _NDP_CONTEXT {
+    PNDP_LINK NdpLink;
+    PNET_LINK_ADDRESS_ENTRY LinkAddress;
+} NDP_CONTEXT, *PNDP_CONTEXT;
+
+//
+// ----------------------------------------------- Internal Function Prototypes
+//
+
+VOID
+NetpNdpAutoconfigurationThread (
+    PVOID Parameter
+    );
+
+KSTATUS
+NetpNdpDuplicateAddressDetection (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Target
+    );
+
+VOID
+NetpNdpProcessRouterAdvertisement (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+VOID
+NetpNdpProcessNeighborSolicitation (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+VOID
+NetpNdpProcessNeighborAdvertisement (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+KSTATUS
+NetpNdpSendNeighborAdvertisement (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    BOOL Solicited
+    );
+
+KSTATUS
+NetpNdpSendNeighborSolicitation (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Source,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    PNETWORK_ADDRESS Target
+    );
+
+VOID
+NetpNdpSendPackets (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Source,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    PNET_PACKET_LIST PacketList,
+    UCHAR Type
+    );
+
+PNDP_CONTEXT
+NetpNdpCreateContext (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress
+    );
+
+VOID
+NetpNdpDestroyContext (
+    PNDP_CONTEXT Context
+    );
+
+VOID
+NetpNdpGetSolicitedNodeMulticastAddress (
+    PNETWORK_ADDRESS Address,
+    PNETWORK_ADDRESS MulticastAddress
+    );
+
+VOID
+NetpNdpRandomDelay (
+    ULONG DelayMax
+    );
+
+PNDP_LINK
+NetpNdpCreateOrLookupLink (
+    PNET_LINK Link
+    );
+
+VOID
+NetpNdpDestroyLink (
+    PNDP_LINK NdpLink
+    );
+
+PNDP_LINK
+NetpNdpLookupLink (
+    PNET_LINK Link
+    );
+
+VOID
+NetpNdpLinkAddReference (
+    PNDP_LINK NdpLink
+    );
+
+VOID
+NetpNdpLinkReleaseReference (
+    PNDP_LINK NdpLink
+    );
+
+COMPARISON_RESULT
+NetpNdpCompareLinkEntries (
+    PRED_BLACK_TREE Tree,
+    PRED_BLACK_TREE_NODE FirstNode,
+    PRED_BLACK_TREE_NODE SecondNode
+    );
+
+PNDP_NEIGHBOR_ENTRY
+NetpNdpCreateOrLookupNeighbor (
+    PNDP_LINK NdpLink,
+    PNETWORK_ADDRESS NetworkAddress,
+    NDP_NEIGHBOR_STATE State
+    );
+
+VOID
+NetpNdpDestroyNeighbor (
+    PNDP_NEIGHBOR_ENTRY Neighbor
+    );
+
+PNDP_NEIGHBOR_ENTRY
+NetpNdpLookupNeighbor (
+    PNDP_LINK NdpLink,
+    PNETWORK_ADDRESS NetworkAddress
+    );
+
+VOID
+NetpNdpUpdateNeighbor (
+    PNDP_LINK NdpLink,
+    PNDP_NEIGHBOR_ENTRY Neighbor,
+    NDP_NEIGHBOR_STATE State,
+    PNETWORK_ADDRESS PhysicalAddress
+    );
+
+COMPARISON_RESULT
+NetpNdpCompareNeighborEntries (
+    PRED_BLACK_TREE Tree,
+    PRED_BLACK_TREE_NODE FirstNode,
+    PRED_BLACK_TREE_NODE SecondNode
+    );
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+BOOL NetNdpDebug = FALSE;
+
+//
+// Stores a global tree of net links that use NDP.
+//
+
+RED_BLACK_TREE NetNdpLinkTree;
+PSHARED_EXCLUSIVE_LOCK NetNdpLinkLock;
+
+//
+// ------------------------------------------------------------------ Functions
+//
+
+VOID
+NetpNdpInitialize (
+    VOID
+    )
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for NDP.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    if (NetNdpDebug == FALSE) {
+        NetNdpDebug = NetGetGlobalDebugFlag();
+    }
+
+    RtlRedBlackTreeInitialize(&NetNdpLinkTree, 0, NetpNdpCompareLinkEntries);
+    NetNdpLinkLock = KeCreateSharedExclusiveLock();
+    if (NetNdpLinkLock == NULL) {
+
+        ASSERT(FALSE);
+
+        return;
+    }
+
+    return;
+}
+
+VOID
+NetpNdpProcessReceivedData (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    )
+
+/*++
+
+Routine Description:
+
+    This routine is called to process a received packet.
+
+Arguments:
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    None. When the function returns, the memory associated with the packet may
+    be reclaimed and reused.
+
+--*/
+
+{
+
+    PICMP6_HEADER Header;
+    PNET_PACKET_BUFFER Packet;
+
+    ASSERT(KeGetRunLevel() == RunLevelLow);
+
+    //
+    // All NDP messages must have the max hop limit set (255), indicating that
+    // they came from a link-local node and were not forwarded by a router.
+    //
+
+    Packet = ReceiveContext->Packet;
+    if ((Packet->Flags & NET_PACKET_FLAG_MAX_HOP_LIMIT) == 0) {
+        return;
+    }
+
+    //
+    // Act based on the ICMPv6 message type. The ICMPv6 module already
+    // validated the ICMPv6 header and its checksum.
+    //
+
+    Header = (PICMP6_HEADER)(Packet->Buffer + Packet->DataOffset);
+    switch (Header->Type) {
+
+    //
+    // Minoca does not currently run in router mode.
+    //
+
+    case ICMP6_MESSAGE_TYPE_NDP_ROUTER_SOLICITATION:
+        break;
+
+    case ICMP6_MESSAGE_TYPE_NDP_ROUTER_ADVERTISEMENT:
+        NetpNdpProcessRouterAdvertisement(ReceiveContext);
+        break;
+
+    case ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_SOLICITATION:
+        NetpNdpProcessNeighborSolicitation(ReceiveContext);
+        break;
+
+    case ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_ADVERTISEMENT:
+        NetpNdpProcessNeighborAdvertisement(ReceiveContext);
+        break;
+
+    case ICMP6_MESSAGE_TYPE_NDP_REDIRECT:
+        break;
+
+    default:
+        break;
+    }
+
+    return;
+}
+
+KSTATUS
+NetpNdpConfigureAddress (
+    PNET_LINK Link,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    BOOL Configure
+    )
+
+/*++
+
+Routine Description:
+
+    This routine configures or dismantles the given link address for use over
+    the network on the given link.
+
+Arguments:
+
+    Link - Supplies a pointer to the link to which the address entry belongs.
+
+    LinkAddress - Supplies a pointer to the link address entry to configure.
+
+    Configure - Supplies a boolean indicating whether or not the link address
+        should be configured for use (TRUE) or taken out of service (FALSE).
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    PIP6_ADDRESS Ip6Address;
+    NETWORK_ADDRESS MulticastAddress;
+    PNDP_CONTEXT NdpContext;
+    PNDP_LINK NdpLink;
+    KSTATUS Status;
+
+    NdpContext = NULL;
+    NdpLink = NULL;
+    Ip6Address = (PIP6_ADDRESS)&(LinkAddress->Address);
+
+    //
+    // The system should not be trying to configure a multicast address.
+    //
+
+    if (IP6_IS_MULTICAST_ADDRESS(Ip6Address->Address) != FALSE) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto NdpConfigureAddressEnd;
+    }
+
+    //
+    // Address configuration requires sending a receiving a few messages, do it
+    // asynchronously by kicking off a thread.
+    //
+
+    if (Configure != FALSE) {
+        NdpLink = NetpNdpLookupLink(Link);
+        if (NdpLink == NULL) {
+            NdpLink = NetpNdpCreateOrLookupLink(Link);
+            if (NdpLink == NULL) {
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto NdpConfigureAddressEnd;
+            }
+        }
+
+        NdpContext = NetpNdpCreateContext(NdpLink, LinkAddress);
+        if (NdpContext == NULL) {
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto NdpConfigureAddressEnd;
+        }
+
+        Status = PsCreateKernelThread(NetpNdpAutoconfigurationThread,
+                                      NdpContext,
+                                      "NdpAutoConfigThread");
+
+        if (!KSUCCESS(Status)) {
+            NetpNdpDestroyContext(NdpContext);
+            goto NdpConfigureAddressEnd;
+        }
+
+    //
+    // Tear down does not require another thread. It is not as complex.
+    //
+
+    } else {
+        if (LinkAddress->Configured == FALSE) {
+            Status = STATUS_INVALID_PARAMETER;
+            goto NdpConfigureAddressEnd;
+        }
+
+        NdpLink = NetpNdpLookupLink(Link);
+        if (NdpLink == NULL) {
+            Status = STATUS_NOT_FOUND;
+            goto NdpConfigureAddressEnd;
+        }
+
+        //
+        // Release reference taken when the address was configured. This
+        // reference is taken by the autoconfiguration thread.
+        //
+
+        NetpNdpLinkReleaseReference(NdpLink);
+
+        //
+        // Leave the solicted-node multicast group that was joined when the
+        // address was configured.
+        //
+
+        NetpNdpGetSolicitedNodeMulticastAddress(&(LinkAddress->Address),
+                                                &MulticastAddress);
+
+        Status = NetLeaveLinkMulticastGroup(Link,
+                                            LinkAddress,
+                                            &MulticastAddress);
+
+        if (!KSUCCESS(Status)) {
+            goto NdpConfigureAddressEnd;
+        }
+    }
+
+NdpConfigureAddressEnd:
+    if (NdpLink != NULL) {
+        NetpNdpLinkReleaseReference(NdpLink);
+    }
+
+    return Status;
+}
+
+//
+// --------------------------------------------------------- Internal Functions
+//
+
+VOID
+NetpNdpAutoconfigurationThread (
+    PVOID Parameter
+    )
+
+/*++
+
+Routine Description:
+
+    This routine attempts to autoconfigure an address for a link. It will use a
+    mix of duplicate address detection, router solicitation, and may possibly
+    kick off DHCPv6 in order to determine an address.
+
+Arguments:
+
+    Parameter - Supplies a pointer supplied by the creator of the thread, in
+        this case a pointer to the NDP context structure.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    NETWORK_DEVICE_INFORMATION Information;
+    PIP6_ADDRESS Ip6Target;
+    PNDP_CONTEXT NdpContext;
+    KSTATUS Status;
+    NETWORK_ADDRESS Target;
+
+    NdpContext = (PNDP_CONTEXT)Parameter;
+
+    //
+    // The link address entry stores the target address to configure. Make a
+    // copy while holding the links lock in order to get a consistent read.
+    // It should not be configured at the moment.
+    //
+
+    KeAcquireQueuedLock(NdpContext->NdpLink->Link->QueuedLock);
+    RtlCopyMemory(&Target,
+                  &(NdpContext->LinkAddress->Address),
+                  sizeof(NETWORK_ADDRESS));
+
+    KeReleaseQueuedLock(NdpContext->NdpLink->Link->QueuedLock);
+
+    //
+    // If the given address is a link-local address, duplicate address
+    // detection needs to be performed.
+    //
+
+    Ip6Target = (PIP6_ADDRESS)&Target;
+    if (IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(Ip6Target->Address) != FALSE) {
+        Status = NetpNdpDuplicateAddressDetection(NdpContext->NdpLink,
+                                                  NdpContext->LinkAddress,
+                                                  &Target);
+
+        if (!KSUCCESS(Status)) {
+            goto NdpAutoconfigurationThreadEnd;
+        }
+
+    //
+    // Otherwise, a global scope address should be determined through router
+    // discovery and possibly DHCPv6.
+    //
+    // TODO: Handle global scope address assignment.
+    //
+
+    } else {
+        Status = STATUS_NOT_IMPLEMENTED;
+        goto NdpAutoconfigurationThreadEnd;
+    }
+
+    //
+    // The address was configured! Tell net core that it's ready to go.
+    //
+
+    RtlZeroMemory(&Information, sizeof(NETWORK_DEVICE_INFORMATION));
+    Information.Version = NETWORK_DEVICE_INFORMATION_VERSION;
+    Information.Flags = NETWORK_DEVICE_FLAG_CONFIGURED;
+    Information.Domain = NetDomainIp6;
+    Information.ConfigurationMethod = NetworkAddressConfigurationStateless;
+    RtlCopyMemory(&(Information.Address), &Target, sizeof(NETWORK_ADDRESS));
+    Status = NetGetSetNetworkDeviceInformation(NdpContext->NdpLink->Link,
+                                               NdpContext->LinkAddress,
+                                               &Information,
+                                               TRUE);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpAutoconfigurationThreadEnd;
+    }
+
+    RtlDebugPrint("NDP Autoconfiguration:\n\t");
+    if (IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(Ip6Target->Address) != FALSE) {
+        NetDebugPrintAddress(&Target);
+        RtlDebugPrint("\n");
+    }
+
+    //
+    // Take an extra reference on the NDP link so that it does not disappear
+    // as long as the address is configured.
+    //
+
+    NetpNdpLinkAddReference(NdpContext->NdpLink);
+
+NdpAutoconfigurationThreadEnd:
+    if (!KSUCCESS(Status)) {
+        RtlDebugPrint("Net: NDP autoconfiguration failed: %d\n", Status);
+    }
+
+    NetpNdpDestroyContext(NdpContext);
+    return;
+}
+
+KSTATUS
+NetpNdpDuplicateAddressDetection (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Target
+    )
+
+/*++
+
+Routine Description:
+
+    This routine performs duplicate address detection. As it must wait for
+    messages to be received, do not call it in a critical code path.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link over which the address
+        detection will be conducted.
+
+    LinkAddress - Supplies a pointer to the link address that owns the address
+        that needs to be checked.
+
+    Target - Supplies a pointer to the network address that needs to be checked
+        for duplication over the local network.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    PNET_LINK Link;
+    BOOL MulticastJoined;
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    NETWORK_ADDRESS SolicitedNodeAddress;
+    NETWORK_ADDRESS SolicitedNodePhysical;
+    KSTATUS Status;
+    ULONG TransmitCount;
+    BOOL TransmitSolicitation;
+    NETWORK_ADDRESS UnspecifiedAddress;
+
+    Link = NdpLink->Link;
+    MulticastJoined = FALSE;
+    Neighbor = NULL;
+
+    //
+    // Duplicate address detection requires the link to join the all-nodes
+    // multicast group and the solicited-node multicast group for the address
+    // in question. The all-nodes group is joined during link initialization
+    // and ensures that the node receives advertisements from a different node
+    // already using the address. The solicited-node group ensures that this
+    // node detects another node running duplicate address detection for the
+    // address.
+    //
+    // Join the solicited-node multicast group for the target address after a
+    // random delay.
+    //
+
+    NetpNdpGetSolicitedNodeMulticastAddress(Target, &SolicitedNodeAddress);
+    NetpNdpRandomDelay(NDP_SOLICITATION_DELAY_MAX);
+    Status = NetJoinLinkMulticastGroup(Link,
+                                       LinkAddress,
+                                       &SolicitedNodeAddress);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpDuplicateAddressDetectionEnd;
+    }
+
+    MulticastJoined = TRUE;
+
+    //
+    // The source for all duplicate address detection messages is the
+    // unspecified address and the destination is the solicited-node multicast
+    // address.
+    //
+
+    RtlZeroMemory(&UnspecifiedAddress, sizeof(NETWORK_ADDRESS));
+    UnspecifiedAddress.Domain = NetDomainIp6;
+    Status = Link->DataLinkEntry->Interface.ConvertToPhysicalAddress(
+                                                        &SolicitedNodeAddress,
+                                                        &SolicitedNodePhysical,
+                                                        NetAddressMulticast);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpDuplicateAddressDetectionEnd;
+    }
+
+    //
+    // Create a tentative neighbor cache entry.
+    //
+
+    Neighbor = NetpNdpCreateOrLookupNeighbor(NdpLink,
+                                             Target,
+                                             NdpNeighborTentative);
+
+    if (Neighbor == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto NdpDuplicateAddressDetectionEnd;
+    }
+
+    //
+    // If the state is not tentative, then a lookup happened, rather than a
+    // create. Abort the duplicate address detection as another thread is
+    // working with this cache entry.
+    //
+
+    if (Neighbor->State != NdpNeighborTentative) {
+        Status = STATUS_TOO_LATE;
+        goto NdpDuplicateAddressDetectionEnd;
+    }
+
+    //
+    // Send neighbor solicitations until a response is received, as indicated
+    // by a neighbor cache entry, or until the retransmit count and timer run
+    // out, at which point the address is considered unique.
+    //
+
+    TransmitSolicitation = TRUE;
+    TransmitCount = NDP_DEFAULT_DUPLICATE_ADDRESS_DETECTION_TRANSMIT_COUNT;
+    while (TRUE) {
+
+        //
+        // If the target address's entry got marked as duplicate, than this
+        // interface cannot be used. Another system on the link has the same
+        // link-layer address.
+        //
+
+        if (Neighbor->State == NdpNeighborDuplicate) {
+            Status = STATUS_DUPLICATE_ENTRY;
+            RtlDebugPrint("NDP: Duplicate Address Detected: ");
+            NetDebugPrintAddress(Target);
+            RtlDebugPrint("\n");
+            goto NdpDuplicateAddressDetectionEnd;
+        }
+
+        if (TransmitSolicitation != FALSE) {
+
+            //
+            // If the transmit count is zero, it means that the system has
+            // waited a full retransmit timeout, after the required number of
+            // solicitations have been sent, without a response. The address
+            // is unique.
+            //
+
+            if (TransmitCount == 0) {
+                Status = STATUS_SUCCESS;
+                break;
+            }
+
+            Status = NetpNdpSendNeighborSolicitation(NdpLink,
+                                                     LinkAddress,
+                                                     &UnspecifiedAddress,
+                                                     &SolicitedNodeAddress,
+                                                     &SolicitedNodePhysical,
+                                                     Target);
+
+            if (!KSUCCESS(Status)) {
+                goto NdpDuplicateAddressDetectionEnd;
+            }
+
+            TransmitCount -= 1;
+            TransmitSolicitation = FALSE;
+        }
+
+        //
+        // Wait for a neighbor advertisement to arrive.
+        //
+
+        Status = KeWaitForEvent(NdpLink->NeighborCacheEvent,
+                                FALSE,
+                                NDP_DEFAULT_RETRANMIT_TIMEOUT);
+
+        if (Status == STATUS_TIMEOUT) {
+            TransmitSolicitation = TRUE;
+
+        } else if (!KSUCCESS(Status)) {
+            goto NdpDuplicateAddressDetectionEnd;
+        }
+    }
+
+NdpDuplicateAddressDetectionEnd:
+    if (!KSUCCESS(Status)) {
+        if (MulticastJoined != FALSE) {
+            NetLeaveLinkMulticastGroup(Link,
+                                       LinkAddress,
+                                       &SolicitedNodeAddress);
+        }
+    }
+
+    return Status;
+}
+
+VOID
+NetpNdpProcessRouterAdvertisement (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    )
+
+/*++
+
+Routine Description:
+
+    This routine handles router advertisement NDP messages.
+
+Arguments:
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    //
+    // TODO: Handle router advertisement messages.
+    //
+
+    return;
+}
+
+VOID
+NetpNdpProcessNeighborSolicitation (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    )
+
+/*++
+
+Routine Description:
+
+    This routine handles neighbor solicitation NDP messages.
+
+Arguments:
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PIP6_ADDRESS Ip6Destination;
+    PIP6_ADDRESS Ip6Source;
+    PNET_LINK Link;
+    PNET_LINK_ADDRESS_ENTRY LinkAddressEntry;
+    PNDP_LINK NdpLink;
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    PNDP_OPTION OptionHeader;
+    ULONG OptionSize;
+    PNET_PACKET_BUFFER Packet;
+    ULONG PacketSize;
+    PNDP_NEIGHBOR_SOLICITATION Solicitation;
+    PNETWORK_ADDRESS SourcePhysical;
+    NETWORK_ADDRESS SourcePhysicalBuffer;
+    KSTATUS Status;
+    NETWORK_ADDRESS Target;
+
+    Ip6Source = (PIP6_ADDRESS)ReceiveContext->Source;
+    Ip6Destination = (PIP6_ADDRESS)ReceiveContext->Destination;
+    Link = ReceiveContext->Link;
+
+    //
+    // If no addresses have been configured on this link, don't bother parsing
+    // it as nobody is interested in the result and the link should not
+    // respond.
+    //
+
+    NdpLink = NetpNdpLookupLink(Link);
+    if (NdpLink == NULL) {
+        goto NdpProcessNeighborSolicitationEnd;
+    }
+
+    Packet = ReceiveContext->Packet;
+    PacketSize = Packet->FooterOffset - Packet->DataOffset;
+    if (PacketSize < sizeof(NDP_NEIGHBOR_SOLICITATION)) {
+        goto NdpProcessNeighborSolicitationEnd;
+    }
+
+    //
+    // Get the target IP address out of the message. Drop the packet if it is
+    // a multicast address.
+    //
+
+    Solicitation = (PNDP_NEIGHBOR_SOLICITATION)(Packet->Buffer +
+                                                Packet->DataOffset);
+
+    if (IP6_IS_MULTICAST_ADDRESS(Solicitation->TargetAddress) != FALSE) {
+        goto NdpProcessNeighborSolicitationEnd;
+    }
+
+    Target.Domain = NetDomainIp6;
+    Target.Port = 0;
+    RtlCopyMemory(Target.Address,
+                  Solicitation->TargetAddress,
+                  IP6_ADDRESS_SIZE);
+
+    //
+    // If supplied, get the source's link-layer address out of the message
+    // options.
+    //
+
+    SourcePhysical = NULL;
+    PacketSize -= sizeof(NDP_NEIGHBOR_SOLICITATION);
+    OptionHeader = (PNDP_OPTION)(Solicitation + 1);
+    while (PacketSize != 0) {
+        OptionSize = OptionHeader->Length * NDP_OPTION_LENGTH_MULTIPLE;
+        if ((OptionSize == 0) || (OptionSize > PacketSize)) {
+            goto NdpProcessNeighborSolicitationEnd;
+        }
+
+        if (OptionHeader->Type == NDP_OPTION_TYPE_SOURCE_LINK_ADDRESS) {
+            if ((OptionSize - sizeof(NDP_OPTION)) != ETHERNET_ADDRESS_SIZE) {
+                goto NdpProcessNeighborSolicitationEnd;
+            }
+
+            SourcePhysical = &SourcePhysicalBuffer;
+            SourcePhysical->Domain = Link->Properties.DataLinkType;
+            SourcePhysical->Port = 0;
+            RtlCopyMemory(SourcePhysical->Address,
+                          (PVOID)(OptionHeader + 1),
+                          ETHERNET_ADDRESS_SIZE);
+        }
+
+        PacketSize -= OptionSize;
+    }
+
+    //
+    // If the source is unspecified, then there must not be a source physical
+    // address specified and the destination should have been a solicited-node
+    // multicast address.
+    //
+
+    if (IP6_IS_ANY_ADDRESS(Ip6Source->Address) != FALSE) {
+        if (IP6_IS_SOLICITED_NODE_MULTICAST_ADDRESS(Ip6Destination->Address) ==
+            FALSE) {
+
+            goto NdpProcessNeighborSolicitationEnd;
+        }
+
+        if (SourcePhysical != NULL) {
+            goto NdpProcessNeighborSolicitationEnd;
+        }
+    }
+
+    if (NetNdpDebug != FALSE) {
+        RtlDebugPrint("NDP RX: Who has ");
+        NetDebugPrintAddress(&Target);
+        RtlDebugPrint("? Tell ");
+        NetDebugPrintAddress(ReceiveContext->Source);
+        if (SourcePhysical != NULL) {
+            RtlDebugPrint(" (");
+            NetDebugPrintAddress(SourcePhysical);
+            RtlDebugPrint(")\n");
+
+        } else {
+            RtlDebugPrint("\n");
+        }
+    }
+
+    Status = NetFindEntryForAddress(Link,
+                                    &Target,
+                                    &LinkAddressEntry);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpProcessNeighborSolicitationEnd;
+    }
+
+    //
+    // If the link address entry is not configured, then it is a "tentative"
+    // target. Special processing applies.
+    //
+
+    if (LinkAddressEntry->Configured == FALSE) {
+
+        //
+        // If the source is unspecified, then another node is also performing
+        // address duplication detection. Do not use the tentative address.
+        // As the NDP multicast packets do not get looped back, this does not
+        // need to check if this node sent the solicitation.
+        //
+
+        if (IP6_IS_ANY_ADDRESS(Ip6Source->Address) != FALSE) {
+            Neighbor = NetpNdpCreateOrLookupNeighbor(NdpLink,
+                                                     &Target,
+                                                     NdpNeighborDuplicate);
+
+            if ((Neighbor != NULL) &&
+                (Neighbor->State != NdpNeighborDuplicate)) {
+
+                NetpNdpUpdateNeighbor(NdpLink,
+                                      Neighbor,
+                                      NdpNeighborDuplicate,
+                                      NULL);
+            }
+
+            NetpNdpLinkReleaseReference(NdpLink);
+        }
+
+        return;
+    }
+
+    //
+    // If the solicitation supplied a link-layer address and the network
+    // address is valid, then save it.
+    //
+
+    if (IP6_IS_ANY_ADDRESS(Ip6Source->Address) == FALSE) {
+
+        //
+        // NDP does not require unicast neighbor solicitations to include the
+        // source link-layer address. The work around is to get the source
+        // physical address from the data link layer. Drop the packets for now.
+        //
+        // TODO: Get source physical address from data link layer.
+        //
+
+        if (SourcePhysical == NULL) {
+            goto NdpProcessNeighborSolicitationEnd;
+        }
+
+        //
+        // TODO: Add solicitation to neighbor cache.
+        //
+
+    }
+
+    //
+    // Respond with a solicited advertisement.
+    //
+
+    NetpNdpSendNeighborAdvertisement(NdpLink,
+                                     LinkAddressEntry,
+                                     (PNETWORK_ADDRESS)Ip6Source,
+                                     SourcePhysical,
+                                     TRUE);
+
+NdpProcessNeighborSolicitationEnd:
+    if (NdpLink != NULL) {
+        NetpNdpLinkReleaseReference(NdpLink);
+    }
+
+    return;
+}
+
+VOID
+NetpNdpProcessNeighborAdvertisement (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    )
+
+/*++
+
+Routine Description:
+
+    This routine handles neighbor advertisement NDP messages.
+
+Arguments:
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PNDP_NEIGHBOR_ADVERTISEMENT Advertisement;
+    PIP6_ADDRESS Ip6Destination;
+    PNET_LINK Link;
+    PNDP_LINK NdpLink;
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    PNDP_OPTION OptionHeader;
+    ULONG OptionSize;
+    PNET_PACKET_BUFFER Packet;
+    ULONG PacketSize;
+    NETWORK_ADDRESS Target;
+    PNETWORK_ADDRESS TargetPhysical;
+    NETWORK_ADDRESS TargetPhysicalBuffer;
+
+    Ip6Destination = (PIP6_ADDRESS)ReceiveContext->Destination;
+    Link = ReceiveContext->Link;
+
+    //
+    // If there is no NDP link context, then no addresses have been configured
+    // on the link and nobody is interested in the advertisement.
+    //
+
+    NdpLink = NetpNdpLookupLink(Link);
+    if (NdpLink == NULL) {
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    Packet = ReceiveContext->Packet;
+    PacketSize = Packet->FooterOffset - Packet->DataOffset;
+    if (PacketSize < sizeof(NDP_NEIGHBOR_ADVERTISEMENT)) {
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    //
+    // Get the target IP address out of the message. Drop the packet if it is a
+    // multicast address.
+    //
+
+    Advertisement = (PNDP_NEIGHBOR_ADVERTISEMENT)(Packet->Buffer +
+                                                  Packet->DataOffset);
+
+    if (IP6_IS_MULTICAST_ADDRESS(Advertisement->TargetAddress) != FALSE) {
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    Target.Domain = NetDomainIp6;
+    Target.Port = 0;
+    RtlCopyMemory(Target.Address,
+                  Advertisement->TargetAddress,
+                  IP6_ADDRESS_SIZE);
+
+    //
+    // If the destination is a multicast address, the solicited flag better be
+    // zero.
+    //
+
+    if ((IP6_IS_MULTICAST_ADDRESS(Ip6Destination->Address) != FALSE) &&
+        ((Advertisement->Flags & NDP_NEIGHBOR_FLAG_SOLICITED) != 0)) {
+
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    //
+    // If supplied, get the target's link-layer address out of the message
+    // options.
+    //
+
+    TargetPhysical = NULL;
+    PacketSize -= sizeof(NDP_NEIGHBOR_SOLICITATION);
+    OptionHeader = (PNDP_OPTION)(Advertisement + 1);
+    while (PacketSize != 0) {
+        OptionSize = OptionHeader->Length * NDP_OPTION_LENGTH_MULTIPLE;
+        if ((OptionSize == 0) || (OptionSize > PacketSize)) {
+            goto NdpProcessNeighborAdvertisementEnd;
+        }
+
+        if (OptionHeader->Type == NDP_OPTION_TYPE_TARGET_LINK_ADDRESS) {
+            if ((OptionSize - sizeof(NDP_OPTION)) != ETHERNET_ADDRESS_SIZE) {
+                goto NdpProcessNeighborAdvertisementEnd;
+            }
+
+            TargetPhysical = &TargetPhysicalBuffer;
+            TargetPhysical->Domain = Link->Properties.DataLinkType;
+            TargetPhysical->Port = 0;
+            RtlCopyMemory(TargetPhysical->Address,
+                          (PVOID)(OptionHeader + 1),
+                          ETHERNET_ADDRESS_SIZE);
+        }
+
+        PacketSize -= OptionSize;
+    }
+
+    //
+    // Try to find a neighbor cache entry for the target.
+    //
+
+    Neighbor = NetpNdpLookupNeighbor(NdpLink, &Target);
+    if (Neighbor == NULL) {
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    //
+    // If the neighbor cache entry is marked tentative, then this link is in
+    // the middle of duplicate address detection and a duplicate was found.
+    // Update the neighbor cache entry.
+    //
+
+    if (Neighbor->State == NdpNeighborTentative) {
+        NetpNdpUpdateNeighbor(NdpLink, Neighbor, NdpNeighborDuplicate, NULL);
+        goto NdpProcessNeighborAdvertisementEnd;
+    }
+
+    //
+    // TODO: Process neighbor advertisements beyond duplicate address detection.
+    //
+
+NdpProcessNeighborAdvertisementEnd:
+    if (NdpLink != NULL) {
+        NetpNdpLinkReleaseReference(NdpLink);
+    }
+
+    return;
+}
+
+KSTATUS
+NetpNdpSendNeighborAdvertisement (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    BOOL Solicited
+    )
+
+/*++
+
+Routine Description:
+
+    This routine allocates, assembles, and sends an NDP advertisement to
+    communicate the physical address of one of the network addresses owned by
+    this machine. This routine returns as soon as the NDP request is
+    successfully queued for transmission.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link to send the advertisement down.
+
+    LinkAddress - Supplies a pointer to the source address of the advertisement.
+
+    Destination - Supplies a pointer to the network address to
+        send the response to.
+
+    DestinationPhysical - Supplies a pointer to the physical address to
+        send the response to.
+
+    Solicited - Supplies a boolean indicating whether ot not the advertisement
+        was prompted by a solicitation.
+
+Return Value:
+
+    STATUS_SUCCESS if the request was successfully sent off.
+
+    STATUS_INSUFFICIENT_RESOURCES if the transmission buffer couldn't be
+    allocated.
+
+    Other errors on other failures.
+
+--*/
+
+{
+
+    PNDP_NEIGHBOR_ADVERTISEMENT Advertisement;
+    NETWORK_ADDRESS AllNodesAddress;
+    NETWORK_ADDRESS AllNodesPhysicalAddress;
+    ULONG BufferFlags;
+    PNET_LINK Link;
+    BOOL LockHeld;
+    PNDP_OPTION OptionHeader;
+    ULONG OptionSize;
+    PNET_PACKET_BUFFER Packet;
+    NET_PACKET_LIST PacketList;
+    ULONG PacketSize;
+    NETWORK_ADDRESS Source;
+    KSTATUS Status;
+
+    NET_INITIALIZE_PACKET_LIST(&PacketList);
+    Link = NdpLink->Link;
+    LockHeld = FALSE;
+
+    //
+    // Determine the size of the packet. If this is not a duplicate address
+    // detection, then a source link-layer address option is added.
+    //
+
+    PacketSize = sizeof(NDP_NEIGHBOR_SOLICITATION);
+    OptionSize = sizeof(NDP_OPTION) + ETHERNET_ADDRESS_SIZE;
+    PacketSize += OptionSize;
+    BufferFlags = NET_ALLOCATE_BUFFER_FLAG_ADD_DEVICE_LINK_HEADERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DEVICE_LINK_FOOTERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DATA_LINK_HEADERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DATA_LINK_FOOTERS;
+
+    Status = NetAllocateBuffer(sizeof(IP6_HEADER),
+                               PacketSize,
+                               0,
+                               Link,
+                               BufferFlags,
+                               &Packet);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpSendNeighborAdvertisementEnd;
+    }
+
+    NET_ADD_PACKET_TO_LIST(Packet, &PacketList);
+
+    //
+    // Initialize the ICMPv6 NDP neighbor advertisement message. For solicited
+    // advertisements, the target address is that received during solicitation.
+    // For unsolicited advertisements, the target address is that for the link
+    // whose link-layer address has changed.
+    //
+
+    Advertisement = Packet->Buffer + Packet->DataOffset;
+    Advertisement->Flags = 0;
+    if ((Solicited != FALSE) &&
+        (IP6_IS_MULTICAST_ADDRESS(Destination->Address) == FALSE)) {
+
+        Advertisement->Flags |= NDP_NEIGHBOR_FLAG_SOLICITED;
+    }
+
+    //
+    // The override flag should be set unless solicited by an anycast address.
+    //
+
+    Advertisement->Flags |= NDP_NEIGHBOR_FLAG_OVERRIDE;
+
+    //
+    // Acquire the link lock to get a consistent read of the link address entry.
+    //
+
+    KeAcquireQueuedLock(Link->QueuedLock);
+    LockHeld = TRUE;
+    if (LinkAddress->Configured == FALSE) {
+        Status = STATUS_NO_NETWORK_CONNECTION;
+        goto NdpSendNeighborAdvertisementEnd;
+    }
+
+    ASSERT(LinkAddress->Address.Domain == NetDomainIp6);
+
+    RtlCopyMemory(&Source, &(LinkAddress->Address), sizeof(NETWORK_ADDRESS));
+    KeReleaseQueuedLock(Link->QueuedLock);
+    LockHeld = FALSE;
+    RtlCopyMemory(Advertisement->TargetAddress,
+                  Source.Address,
+                  IP6_ADDRESS_SIZE);
+
+    //
+    // Add the NDP target link-layer address option.
+    //
+
+    OptionHeader = (PNDP_OPTION)(Advertisement + 1);
+    OptionHeader->Type = NDP_OPTION_TYPE_TARGET_LINK_ADDRESS;
+    OptionHeader->Length = OptionSize / NDP_OPTION_LENGTH_MULTIPLE;
+    RtlCopyMemory((PVOID)(OptionHeader + 1),
+                  &(LinkAddress->PhysicalAddress.Address),
+                  ETHERNET_ADDRESS_SIZE);
+
+    //
+    // Craft up a solicited-node multicast address based on the given query
+    // address.
+    //
+
+    if ((Solicited == FALSE) ||
+        (IP6_IS_ANY_ADDRESS(((PIP6_ADDRESS)Destination)->Address) != FALSE)) {
+
+        RtlZeroMemory(&AllNodesAddress, sizeof(NETWORK_ADDRESS));
+        AllNodesAddress.Domain = NetDomainIp6;
+        RtlCopyMemory(AllNodesAddress.Address,
+                      NetIp6AllNodesMulticastAddress,
+                      IP6_ADDRESS_SIZE);
+
+        Status = Link->DataLinkEntry->Interface.ConvertToPhysicalAddress(
+                                                      &AllNodesAddress,
+                                                      &AllNodesPhysicalAddress,
+                                                      NetAddressMulticast);
+
+        if (!KSUCCESS(Status)) {
+            goto NdpSendNeighborAdvertisementEnd;
+        }
+
+        Destination = &AllNodesAddress;
+        DestinationPhysical = &AllNodesPhysicalAddress;
+    }
+
+    //
+    // Send the neighbor advertisement message down to ICMPv6.
+    //
+
+    NetpNdpSendPackets(NdpLink,
+                       LinkAddress,
+                       &Source,
+                       Destination,
+                       DestinationPhysical,
+                       &PacketList,
+                       ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_ADVERTISEMENT);
+
+NdpSendNeighborAdvertisementEnd:
+    if (LockHeld != FALSE) {
+        KeReleaseQueuedLock(Link->QueuedLock);
+    }
+
+    if (!KSUCCESS(Status)) {
+        NetDestroyBufferList(&PacketList);
+    }
+
+    return Status;
+}
+
+KSTATUS
+NetpNdpSendNeighborSolicitation (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Source,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    PNETWORK_ADDRESS Target
+    )
+
+/*++
+
+Routine Description:
+
+    This routine allocates, assembles, and sends an NDP request to translate
+    the given network address into a physical address. This routine returns
+    as soon as the NDP request is successfully queued for transmission.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link to send the request down.
+
+    LinkAddress - Supplies a pointer to the link address associated with the
+        solicitation. The link address may be in the middle of configuration,
+        so its address is not used.
+
+    Source - Supplies a pointer to the source address of the request.
+
+    SourcePhysical - Supplies a pointer the source's physical address.
+
+    Destination - Supplies a pointer to the destination address of the message.
+
+    DestinationPhysical - Supplies a pointer the destinations's physical
+        address.
+
+    Target - Supplies a pointer to the network address to ask about.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    ULONG BufferFlags;
+    BOOL DuplicateDetection;
+    PIP6_ADDRESS Ip6Source;
+    PIP6_ADDRESS Ip6Target;
+    PNDP_OPTION OptionHeader;
+    ULONG OptionSize;
+    PNET_PACKET_BUFFER Packet;
+    NET_PACKET_LIST PacketList;
+    ULONG PacketSize;
+    PNDP_NEIGHBOR_SOLICITATION Solicitation;
+    KSTATUS Status;
+
+    ASSERT(Target->Domain == NetDomainIp6);
+
+    NET_INITIALIZE_PACKET_LIST(&PacketList);
+    Ip6Target = (PIP6_ADDRESS)Target;
+    if (IP6_IS_MULTICAST_ADDRESS(Ip6Target->Address) != FALSE) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto NdpSendNeighborSolicitationEnd;
+    }
+
+    Ip6Source = (PIP6_ADDRESS)Source;
+    DuplicateDetection = FALSE;
+    if (IP6_IS_ANY_ADDRESS(Ip6Source->Address) != FALSE) {
+        DuplicateDetection = TRUE;
+    }
+
+    //
+    // Determine the size of the packet. If this is not a duplicate address
+    // detection, then a source link-layer address option is added.
+    //
+
+    PacketSize = sizeof(NDP_NEIGHBOR_SOLICITATION);
+    if (DuplicateDetection == FALSE) {
+        OptionSize = sizeof(NDP_OPTION) + ETHERNET_ADDRESS_SIZE;
+        PacketSize += OptionSize;
+    }
+
+    BufferFlags = NET_ALLOCATE_BUFFER_FLAG_ADD_DEVICE_LINK_HEADERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DEVICE_LINK_FOOTERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DATA_LINK_HEADERS |
+                  NET_ALLOCATE_BUFFER_FLAG_ADD_DATA_LINK_FOOTERS;
+
+    Status = NetAllocateBuffer(sizeof(IP6_HEADER),
+                               PacketSize,
+                               0,
+                               NdpLink->Link,
+                               BufferFlags,
+                               &Packet);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpSendNeighborSolicitationEnd;
+    }
+
+    NET_ADD_PACKET_TO_LIST(Packet, &PacketList);
+
+    //
+    // Initialize the ICMPv6 NDP neighbor solicitation message.
+    //
+
+    Solicitation = (PNDP_NEIGHBOR_SOLICITATION)(Packet->Buffer +
+                                                Packet->DataOffset);
+
+    Solicitation->Reserved = 0;
+    RtlCopyMemory(&(Solicitation->TargetAddress),
+                  &(Ip6Target->Address),
+                  IP6_ADDRESS_SIZE);
+
+    //
+    // Add the NDP source link-layer address option if this is not for
+    // duplicate address detection.
+    //
+
+    if (DuplicateDetection == FALSE) {
+        OptionHeader = (PNDP_OPTION)(Solicitation + 1);
+        OptionHeader->Type = NDP_OPTION_TYPE_SOURCE_LINK_ADDRESS;
+        OptionHeader->Length = OptionSize / NDP_OPTION_LENGTH_MULTIPLE;
+        RtlCopyMemory((PVOID)(OptionHeader + 1),
+                      LinkAddress->PhysicalAddress.Address,
+                      ETHERNET_ADDRESS_SIZE);
+    }
+
+    NetpNdpSendPackets(NdpLink,
+                       LinkAddress,
+                       Source,
+                       Destination,
+                       DestinationPhysical,
+                       &PacketList,
+                       ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_SOLICITATION);
+
+NdpSendNeighborSolicitationEnd:
+    if (!KSUCCESS(Status)) {
+        NetDestroyBufferList(&PacketList);
+    }
+
+    return Status;
+}
+
+VOID
+NetpNdpSendPackets (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    PNETWORK_ADDRESS Source,
+    PNETWORK_ADDRESS Destination,
+    PNETWORK_ADDRESS DestinationPhysical,
+    PNET_PACKET_LIST PacketList,
+    UCHAR Type
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends a list of NDP packets out over the provided link to the
+    specified destination. It adds the ICMPv6 and IPv6 headers and sends the
+    packets down the stack.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link over which to send the packet.
+
+    LinkAddress - Supplies a pointer to the link address for which the packet
+        is being sent.
+
+    Source - Supplies a pointer to the IPv6 source address.
+
+    Destination - Supplies a pointer to the IPv6 destination address.
+
+    DestinationPhysical - Supplies a pointer to the physical address of the
+        destination.
+
+    PacketList - Supplies a pointer to the list of packets to send.
+
+    Type - Supplies the ICMPv6 message type for the packets.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    USHORT Checksum;
+    PLIST_ENTRY CurrentEntry;
+    PICMP6_HEADER Icmp6Header;
+    ULONG Icmp6Length;
+    PIP6_HEADER Ip6Header;
+    PNET_PACKET_BUFFER Packet;
+    ULONG PayloadLength;
+    PNET_DATA_LINK_SEND Send;
+    KSTATUS Status;
+    ULONG VersionClassFlow;
+
+    //
+    // For each packet in the list, add an ICMPv6 and IPv6 header.
+    //
+
+    CurrentEntry = PacketList->Head.Next;
+    while (CurrentEntry != &(PacketList->Head)) {
+        Packet = LIST_VALUE(CurrentEntry, NET_PACKET_BUFFER, ListEntry);
+        CurrentEntry = CurrentEntry->Next;
+
+        //
+        // Initialize the ICMPv6 header. The data offset should already be
+        // set to the ICMPv6 header as all NDP messages include an ICMPv6
+        // header.
+        //
+
+        Icmp6Header = (PICMP6_HEADER)(Packet->Buffer + Packet->DataOffset);
+        Icmp6Header->Type = Type;
+        Icmp6Header->Code = 0;
+        Icmp6Header->Checksum = 0;
+
+        //
+        // Calculate the ICMPv6 checksum.
+        //
+
+        Icmp6Length = Packet->FooterOffset - Packet->DataOffset;
+        Checksum = NetChecksumPseudoHeaderAndData(
+                                               LinkAddress->Network,
+                                               Icmp6Header,
+                                               Icmp6Length,
+                                               (PNETWORK_ADDRESS)Source,
+                                               Destination,
+                                               SOCKET_INTERNET_PROTOCOL_ICMP6);
+
+        Icmp6Header->Checksum = Checksum;
+
+        //
+        // Now add the IPv6 header.
+        //
+
+        PayloadLength = Packet->FooterOffset - Packet->DataOffset;
+        if (PayloadLength > IP6_MAX_PAYLOAD_LENGTH) {
+            Status = STATUS_MESSAGE_TOO_LONG;
+            goto NdpSendPacketsEnd;
+        }
+
+        ASSERT(Packet->DataOffset >= sizeof(IP6_HEADER));
+
+        Packet->DataOffset -= sizeof(IP6_HEADER);
+        Ip6Header = (PIP6_HEADER)(Packet->Buffer + Packet->DataOffset);
+        VersionClassFlow = (IP6_VERSION << IP6_VERSION_SHIFT) &
+                           IP6_VERSION_MASK;
+
+        Ip6Header->VersionClassFlow = CPU_TO_NETWORK32(VersionClassFlow);
+        Ip6Header->PayloadLength = CPU_TO_NETWORK16((USHORT)PayloadLength);
+        Ip6Header->NextHeader = SOCKET_INTERNET_PROTOCOL_ICMP6;
+        Ip6Header->HopLimit = NDP_IP6_HOP_LIMIT;
+        RtlCopyMemory(Ip6Header->SourceAddress,
+                      Source->Address,
+                      IP6_ADDRESS_SIZE);
+
+        RtlCopyMemory(Ip6Header->DestinationAddress,
+                      Destination->Address,
+                      IP6_ADDRESS_SIZE);
+    }
+
+    Send = NdpLink->Link->DataLinkEntry->Interface.Send;
+    Status = Send(NdpLink->Link->DataLinkContext,
+                  PacketList,
+                  &(LinkAddress->PhysicalAddress),
+                  DestinationPhysical,
+                  IP6_PROTOCOL_NUMBER);
+
+    if (!KSUCCESS(Status)) {
+        goto NdpSendPacketsEnd;
+    }
+
+NdpSendPacketsEnd:
+    if (!KSUCCESS(Status)) {
+        NetDestroyBufferList(PacketList);
+    }
+
+    return;
+}
+
+PNDP_CONTEXT
+NetpNdpCreateContext (
+    PNDP_LINK NdpLink,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creates an NDP context.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link for the context.
+
+    LinkAddress - Supplies a pointer to the network link address for the NDP
+        context.
+
+Return Value:
+
+    Returns a pointer to the newly allocated NDP context on success, or NULL
+    on failure.
+
+--*/
+
+{
+
+    PNDP_CONTEXT Context;
+
+    Context = MmAllocatePagedPool(sizeof(NDP_CONTEXT), NDP_ALLOCATION_TAG);
+    if (Context == NULL) {
+        return NULL;
+    }
+
+    RtlZeroMemory(Context, sizeof(NDP_CONTEXT));
+    NetpNdpLinkAddReference(NdpLink);
+    Context->NdpLink = NdpLink;
+    Context->LinkAddress = LinkAddress;
+    return Context;
+}
+
+VOID
+NetpNdpDestroyContext (
+    PNDP_CONTEXT Context
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys the given NDP context.
+
+Arguments:
+
+    Context - Supplies a pointer to the NDP context to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ASSERT(Context->NdpLink != NULL);
+
+    NetpNdpLinkReleaseReference(Context->NdpLink);
+    MmFreePagedPool(Context);
+    return;
+}
+
+VOID
+NetpNdpGetSolicitedNodeMulticastAddress (
+    PNETWORK_ADDRESS Address,
+    PNETWORK_ADDRESS MulticastAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creates a solicited-node multicast address for the given IPv6
+    address.
+
+Arguments:
+
+    Address - Supplies a pointer to the IPv6 address on which to base the
+        solicited-node multicast address.
+
+    MulticastAddress - Supplies a pointer to a network address that is
+        initialized with the given address's solicited-node multicast address.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PIP6_ADDRESS Ip6Address;
+    PIP6_ADDRESS Ip6Multicast;
+
+    Ip6Address = (PIP6_ADDRESS)Address;
+    Ip6Multicast = (PIP6_ADDRESS)MulticastAddress;
+    RtlZeroMemory(Ip6Multicast, sizeof(IP6_ADDRESS));
+    Ip6Multicast->Domain = NetDomainIp6;
+    RtlCopyMemory(Ip6Multicast->Address,
+                  NetIp6SolicitedNodeMulticastPrefix,
+                  IP6_ADDRESS_SIZE);
+
+    Ip6Multicast->Address[3] |= CPU_TO_NETWORK32(0x00FFFFFF) &
+                                Ip6Address->Address[3];
+
+    return;
+}
+
+VOID
+NetpNdpRandomDelay (
+    ULONG DelayMax
+    )
+
+/*++
+
+Routine Description:
+
+    This routine delays for a random amount of time between 0 and the given
+    delay maximum.
+
+Arguments:
+
+    DelayMax - Supplies the maximum delay, in milliseconds.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG Delay;
+    ULONGLONG DelayInMicroseconds;
+
+    //
+    // The random delay is selected from the range (0, MaxResponseTime].
+    //
+
+    KeGetRandomBytes(&Delay, sizeof(Delay));
+    Delay = (Delay % DelayMax) + 1;
+    DelayInMicroseconds = Delay * MICROSECONDS_PER_MILLISECOND;
+    KeDelayExecution(FALSE, FALSE, DelayInMicroseconds);
+    return;
+}
+
+PNDP_LINK
+NetpNdpCreateOrLookupLink (
+    PNET_LINK Link
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creates an NDP link associated with the given link and
+    attempts to insert it into the tree. If an existing match is found, then
+    the existing link is returned.
+
+Arguments:
+
+    Link - Supplies a pointer to the network link for which the NDP link is
+        to be created.
+
+Return Value:
+
+    Returns a pointer to the newly allocated NDP link on success or NULL on
+    failure.
+
+--*/
+
+{
+
+    PRED_BLACK_TREE_NODE FoundNode;
+    PNDP_LINK NdpLink;
+    PNDP_LINK NewNdpLink;
+    NDP_LINK SearchLink;
+
+    NdpLink = NULL;
+    NewNdpLink = MmAllocatePagedPool(sizeof(NDP_LINK), NDP_ALLOCATION_TAG);
+    if (NewNdpLink == NULL) {
+        goto CreateOrLookupLinkEnd;
+    }
+
+    RtlZeroMemory(NewNdpLink, sizeof(NDP_LINK));
+    NewNdpLink->ReferenceCount = 1;
+    NetLinkAddReference(Link);
+    NewNdpLink->Link = Link;
+    RtlRedBlackTreeInitialize(&(NewNdpLink->NeighborCacheTree),
+                              0,
+                              NetpNdpCompareNeighborEntries);
+
+    NewNdpLink->SharedExclusiveLock = KeCreateSharedExclusiveLock();
+    if (NewNdpLink->SharedExclusiveLock == NULL) {
+        goto CreateOrLookupLinkEnd;
+    }
+
+    NewNdpLink->NeighborCacheEvent = KeCreateEvent(NULL);
+    if (NewNdpLink->NeighborCacheEvent == NULL) {
+        goto CreateOrLookupLinkEnd;
+    }
+
+    //
+    // Attempt to insert the new NDP link into the tree. If an existing link
+    // is found, use that one and destroy the new one.
+    //
+
+    SearchLink.Link = Link;
+    KeAcquireSharedExclusiveLockExclusive(NetNdpLinkLock);
+    FoundNode = RtlRedBlackTreeSearch(&NetNdpLinkTree, &(SearchLink.Node));
+    if (FoundNode == NULL) {
+        RtlRedBlackTreeInsert(&NetNdpLinkTree, &(NewNdpLink->Node));
+        NdpLink = NewNdpLink;
+        NewNdpLink = NULL;
+
+    } else {
+        NdpLink = RED_BLACK_TREE_VALUE(FoundNode, NDP_LINK, Node);
+        NetpNdpLinkAddReference(NdpLink);
+    }
+
+    KeReleaseSharedExclusiveLockExclusive(NetNdpLinkLock);
+
+CreateOrLookupLinkEnd:
+    if (NewNdpLink != NULL) {
+        NetpNdpLinkReleaseReference(NewNdpLink);
+    }
+
+    return NdpLink;
+}
+
+VOID
+NetpNdpDestroyLink (
+    PNDP_LINK NdpLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys an NDP link and all of its resources.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    PRED_BLACK_TREE_NODE Node;
+
+    ASSERT(NdpLink->ReferenceCount == 0);
+    ASSERT(NdpLink->Node.Parent == NULL);
+
+    if (NdpLink->SharedExclusiveLock != NULL) {
+        KeDestroySharedExclusiveLock(NdpLink->SharedExclusiveLock);
+    }
+
+    if (NdpLink->NeighborCacheEvent != NULL) {
+        KeDestroyEvent(NdpLink->NeighborCacheEvent);
+    }
+
+    while (TRUE) {
+        Node = RtlRedBlackTreeGetLowestNode(&(NdpLink->NeighborCacheTree));
+        if (Node == NULL) {
+            break;
+        }
+
+        Neighbor = RED_BLACK_TREE_VALUE(Node, NDP_NEIGHBOR_ENTRY, Node);
+        RtlRedBlackTreeRemove(&(NdpLink->NeighborCacheTree), Node);
+        NetpNdpDestroyNeighbor(Neighbor);
+    }
+
+    NetLinkReleaseReference(NdpLink->Link);
+    MmFreePagedPool(NdpLink);
+    return;
+}
+
+PNDP_LINK
+NetpNdpLookupLink (
+    PNET_LINK Link
+    )
+
+/*++
+
+Routine Description:
+
+    This routine finds an NDP link associated with the given network link. The
+    caller is expected to release a reference on the NDP link.
+
+Arguments:
+
+    Link - Supplies a pointer to the network link, which is used to look up the
+        NDP link.
+
+Return Value:
+
+    Returns a pointer to the matching NDP link on success or NULL on failure.
+
+--*/
+
+{
+
+    PRED_BLACK_TREE_NODE FoundNode;
+    PNDP_LINK NdpLink;
+    NDP_LINK SearchLink;
+
+    NdpLink = NULL;
+    SearchLink.Link = Link;
+    KeAcquireSharedExclusiveLockShared(NetNdpLinkLock);
+    FoundNode = RtlRedBlackTreeSearch(&NetNdpLinkTree, &(SearchLink.Node));
+    if (FoundNode == NULL) {
+        goto FindLinkEnd;
+    }
+
+    NdpLink = RED_BLACK_TREE_VALUE(FoundNode, NDP_LINK, Node);
+    NetpNdpLinkAddReference(NdpLink);
+
+FindLinkEnd:
+    KeReleaseSharedExclusiveLockShared(NetNdpLinkLock);
+    return NdpLink;
+}
+
+VOID
+NetpNdpLinkAddReference (
+    PNDP_LINK NdpLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine increments the reference count of an NDP link.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG OldReferenceCount;
+
+    OldReferenceCount = RtlAtomicAdd32(&(NdpLink->ReferenceCount), 1);
+
+    ASSERT(OldReferenceCount < 0x10000000);
+
+    return;
+}
+
+VOID
+NetpNdpLinkReleaseReference (
+    PNDP_LINK NdpLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine releases a reference on an NDP link.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    BOOL Destroy;
+    ULONG OldReferenceCount;
+
+    //
+    // Acquire the tree lock exclusively before decrementing the reference
+    // count. This is necessary to make the decrement and removal from the tree
+    // atomic.
+    //
+
+    Destroy = FALSE;
+    KeAcquireSharedExclusiveLockExclusive(NetNdpLinkLock);
+    OldReferenceCount = RtlAtomicAdd32(&(NdpLink->ReferenceCount), (ULONG)-1);
+
+    ASSERT((OldReferenceCount != 0) && (OldReferenceCount < 0x10000000));
+
+    //
+    // If the last reference was just released, then the NDP link entry can be
+    // safely removed from the tree and destroyed.
+    //
+
+    if (OldReferenceCount == 1) {
+        if (NdpLink->Node.Parent != NULL) {
+            RtlRedBlackTreeRemove(&NetNdpLinkTree, &(NdpLink->Node));
+            NdpLink->Node.Parent = NULL;
+        }
+
+        Destroy = TRUE;
+    }
+
+    KeReleaseSharedExclusiveLockExclusive(NetNdpLinkLock);
+    if (Destroy != FALSE) {
+        NetpNdpDestroyLink(NdpLink);
+    }
+
+    return;
+}
+
+COMPARISON_RESULT
+NetpNdpCompareLinkEntries (
+    PRED_BLACK_TREE Tree,
+    PRED_BLACK_TREE_NODE FirstNode,
+    PRED_BLACK_TREE_NODE SecondNode
+    )
+
+/*++
+
+Routine Description:
+
+    This routine compares two Red-Black tree nodes.
+
+Arguments:
+
+    Tree - Supplies a pointer to the Red-Black tree that owns both nodes.
+
+    FirstNode - Supplies a pointer to the left side of the comparison.
+
+    SecondNode - Supplies a pointer to the second side of the comparison.
+
+Return Value:
+
+    Same if the two nodes have the same value.
+
+    Ascending if the first node is less than the second node.
+
+    Descending if the second node is less than the first node.
+
+--*/
+
+{
+
+    PNDP_LINK FirstNdpLink;
+    PNDP_LINK SecondNdpLink;
+
+    FirstNdpLink = RED_BLACK_TREE_VALUE(FirstNode, NDP_LINK, Node);
+    SecondNdpLink = RED_BLACK_TREE_VALUE(SecondNode, NDP_LINK, Node);
+    if (FirstNdpLink->Link == SecondNdpLink->Link) {
+        return ComparisonResultSame;
+
+    } else if (FirstNdpLink->Link < SecondNdpLink->Link) {
+        return ComparisonResultAscending;
+    }
+
+    return ComparisonResultDescending;
+}
+
+PNDP_NEIGHBOR_ENTRY
+NetpNdpCreateOrLookupNeighbor (
+    PNDP_LINK NdpLink,
+    PNETWORK_ADDRESS NetworkAddress,
+    NDP_NEIGHBOR_STATE State
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creates a new neighbor cache entry with the given address and
+    inserts it into the tree. If an existing neighbor cache entry for the given
+    address is found, however, the existing neighbor cache entry is returned.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link to which the neighbor cache
+        entry belongs.
+
+    NetworkAddress - Supplies a pointer to the network layer address that is the
+        neighbor's key in the cache.
+
+    State - Supplies the initial state for the newly created cache entry.
+
+Return Value:
+
+    Returns a pointer to the new or found neighbor cache entry on success, or
+    NULL on failure.
+
+--*/
+
+{
+
+    PRED_BLACK_TREE_NODE FoundNode;
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    PNDP_NEIGHBOR_ENTRY NewNeighbor;
+    NDP_NEIGHBOR_ENTRY SearchNeighbor;
+
+    Neighbor = NULL;
+    NewNeighbor = MmAllocatePagedPool(sizeof(NDP_NEIGHBOR_ENTRY),
+                                      NDP_ALLOCATION_TAG);
+
+    if (NewNeighbor == NULL) {
+        goto NdpCreateOrLookupNeighborEnd;
+    }
+
+    RtlZeroMemory(NewNeighbor, sizeof(NDP_NEIGHBOR_ENTRY));
+    NewNeighbor->State = State;
+    RtlCopyMemory(&(NewNeighbor->NetworkAddress),
+                  NetworkAddress,
+                  sizeof(NETWORK_ADDRESS));
+
+    RtlCopyMemory(&(SearchNeighbor.NetworkAddress),
+                  NetworkAddress,
+                  sizeof(NETWORK_ADDRESS));
+
+    KeAcquireSharedExclusiveLockExclusive(NdpLink->SharedExclusiveLock);
+    FoundNode = RtlRedBlackTreeSearch(&(NdpLink->NeighborCacheTree),
+                                      &(SearchNeighbor.Node));
+
+    if (FoundNode == NULL) {
+        RtlRedBlackTreeInsert(&(NdpLink->NeighborCacheTree),
+                              &(NewNeighbor->Node));
+
+        Neighbor = NewNeighbor;
+        NewNeighbor = NULL;
+
+    } else {
+        Neighbor = RED_BLACK_TREE_VALUE(FoundNode, NDP_NEIGHBOR_ENTRY, Node);
+    }
+
+    KeReleaseSharedExclusiveLockExclusive(NdpLink->SharedExclusiveLock);
+
+NdpCreateOrLookupNeighborEnd:
+    if (NewNeighbor != NULL) {
+        NetpNdpDestroyNeighbor(NewNeighbor);
+    }
+
+    return Neighbor;
+}
+
+VOID
+NetpNdpDestroyNeighbor (
+    PNDP_NEIGHBOR_ENTRY Neighbor
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys a neighbor cache entry and all its resources.
+
+Arguments:
+
+    Neighbor - Supplies a pointer to the neighbor cache entry to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    MmFreePagedPool(Neighbor);
+    return;
+}
+
+PNDP_NEIGHBOR_ENTRY
+NetpNdpLookupNeighbor (
+    PNDP_LINK NdpLink,
+    PNETWORK_ADDRESS NetworkAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine looks for a neighbor cache entry associated with the given
+    network address.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link context that owns the cache.
+
+    NetworkAddress - Supplies a pointer to the network address to use as the
+        cache key.
+
+Return Value:
+
+    Returns a pointer to the found neighbor cache entry on success, or NULL on
+    failure.
+
+--*/
+
+{
+
+    PRED_BLACK_TREE_NODE FoundNode;
+    PNDP_NEIGHBOR_ENTRY Neighbor;
+    NDP_NEIGHBOR_ENTRY SearchNeighbor;
+
+    Neighbor = NULL;
+    RtlCopyMemory(&(SearchNeighbor.NetworkAddress),
+                  NetworkAddress,
+                  sizeof(NETWORK_ADDRESS));
+
+    KeAcquireSharedExclusiveLockShared(NdpLink->SharedExclusiveLock);
+    FoundNode = RtlRedBlackTreeSearch(&(NdpLink->NeighborCacheTree),
+                                      &(SearchNeighbor.Node));
+
+    if (FoundNode == NULL) {
+        goto LookupNeighborEnd;
+    }
+
+    Neighbor = RED_BLACK_TREE_VALUE(FoundNode, NDP_NEIGHBOR_ENTRY, Node);
+
+LookupNeighborEnd:
+    KeReleaseSharedExclusiveLockShared(NdpLink->SharedExclusiveLock);
+    return Neighbor;
+}
+
+VOID
+NetpNdpUpdateNeighbor (
+    PNDP_LINK NdpLink,
+    PNDP_NEIGHBOR_ENTRY Neighbor,
+    NDP_NEIGHBOR_STATE State,
+    PNETWORK_ADDRESS PhysicalAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine updates a neighbor cache entry, signaling the neighbor cache
+    change event.
+
+Arguments:
+
+    NdpLink - Supplies a pointer to the NDP link to which the neighbor belongs.
+
+    Neighbor - Supplies a pointer to the neighbor that needs to be changed.
+
+    State - Supplies the state to set in the neighbor cache entry.
+
+    PhysicalAddress - Supplies an optional pointer to the physical address of
+        the neighbor.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    COMPARISON_RESULT Result;
+    BOOL Updated;
+
+    Updated = FALSE;
+    KeAcquireSharedExclusiveLockExclusive(NdpLink->SharedExclusiveLock);
+    if (Neighbor->State != State) {
+        Neighbor->State = State;
+        Updated = TRUE;
+    }
+
+    if (PhysicalAddress != NULL) {
+        Result = NetCompareNetworkAddresses(&(Neighbor->PhysicalAddress),
+                                            PhysicalAddress);
+
+        if (Result != ComparisonResultSame) {
+            RtlCopyMemory(&(Neighbor->PhysicalAddress),
+                          PhysicalAddress,
+                          sizeof(NETWORK_ADDRESS));
+
+            Updated = TRUE;
+        }
+    }
+
+    KeReleaseSharedExclusiveLockExclusive(NdpLink->SharedExclusiveLock);
+    if (Updated != FALSE) {
+        KeSignalEvent(NdpLink->NeighborCacheEvent, SignalOptionPulse);
+    }
+
+    return;
+}
+
+COMPARISON_RESULT
+NetpNdpCompareNeighborEntries (
+    PRED_BLACK_TREE Tree,
+    PRED_BLACK_TREE_NODE FirstNode,
+    PRED_BLACK_TREE_NODE SecondNode
+    )
+
+/*++
+
+Routine Description:
+
+    This routine compares two Red-Black tree nodes.
+
+Arguments:
+
+    Tree - Supplies a pointer to the Red-Black tree that owns both nodes.
+
+    FirstNode - Supplies a pointer to the left side of the comparison.
+
+    SecondNode - Supplies a pointer to the second side of the comparison.
+
+Return Value:
+
+    Same if the two nodes have the same value.
+
+    Ascending if the first node is less than the second node.
+
+    Descending if the second node is less than the first node.
+
+--*/
+
+{
+
+    PNDP_NEIGHBOR_ENTRY FirstEntry;
+    COMPARISON_RESULT Result;
+    PNDP_NEIGHBOR_ENTRY SecondEntry;
+
+    FirstEntry = RED_BLACK_TREE_VALUE(FirstNode, NDP_NEIGHBOR_ENTRY, Node);
+    SecondEntry = RED_BLACK_TREE_VALUE(SecondNode, NDP_NEIGHBOR_ENTRY, Node);
+    Result = NetCompareNetworkAddresses(&(FirstEntry->NetworkAddress),
+                                        &(SecondEntry->NetworkAddress));
+
+    return Result;
+}
+

+ 121 - 0
drivers/net/netcore/ipv6/ndp.h

@@ -0,0 +1,121 @@
+/*++
+
+Copyright (c) 2017 Minoca Corp.
+
+    This file is licensed under the terms of the GNU General Public License
+    version 3. Alternative licensing terms are available. Contact
+    info@minocacorp.com for details. See the LICENSE file at the root of this
+    project for complete licensing information.
+
+Module Name:
+
+    ndp.h
+
+Abstract:
+
+    This header contains definitions for Neighbor Discovery Protocol for IPv6.
+    It is a sub-protocol of ICMPv6.
+
+Author:
+
+    Chris Stevens 7-Sept-2017
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// --------------------------------------------------------------------- Macros
+//
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+//
+// -------------------------------------------------------- Function Prototypes
+//
+
+VOID
+NetpNdpInitialize (
+    VOID
+    );
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for NDP.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
+VOID
+NetpNdpProcessReceivedData (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+/*++
+
+Routine Description:
+
+    This routine is called to process a received packet.
+
+Arguments:
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    None. When the function returns, the memory associated with the packet may
+    be reclaimed and reused.
+
+--*/
+
+KSTATUS
+NetpNdpConfigureAddress (
+    PNET_LINK Link,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress,
+    BOOL Configure
+    );
+
+/*++
+
+Routine Description:
+
+    This routine configures or dismantles the given link address for use over
+    the network on the given link.
+
+Arguments:
+
+    Link - Supplies a pointer to the link to which the address entry belongs.
+
+    LinkAddress - Supplies a pointer to the link address entry to configure.
+
+    Configure - Supplies a boolean indicating whether or not the link address
+        should be configured for use (TRUE) or taken out of service (FALSE).
+
+Return Value:
+
+    Status code.
+
+--*/
+

+ 2 - 1
include/minoca/devinfo/net.h

@@ -86,7 +86,8 @@ typedef enum _NETWORK_ADDRESS_CONFIGURATION_METHOD {
     NetworkAddressConfigurationInvalid,
     NetworkAddressConfigurationNone,
     NetworkAddressConfigurationStatic,
-    NetworkAddressConfigurationDhcp
+    NetworkAddressConfigurationDhcp,
+    NetworkAddressConfigurationStateless,
 } NETWORK_ADDRESS_CONFIGURATION_METHOD, *PNETWORK_ADDRESS_CONFIGURATION_METHOD;
 
 /*++

+ 38 - 3
include/minoca/net/icmp6.h

@@ -41,6 +41,11 @@ Author:
 #define ICMP6_MESSAGE_TYPE_MLD_QUERY 130
 #define ICMP6_MESSAGE_TYPE_MLD_REPORT 131
 #define ICMP6_MESSAGE_TYPE_MLD_DONE 132
+#define ICMP6_MESSAGE_TYPE_NDP_ROUTER_SOLICITATION 133
+#define ICMP6_MESSAGE_TYPE_NDP_ROUTER_ADVERTISEMENT 134
+#define ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_SOLICITATION 135
+#define ICMP6_MESSAGE_TYPE_NDP_NEIGHBOR_ADVERTISEMENT 136
+#define ICMP6_MESSAGE_TYPE_NDP_REDIRECT 137
 #define ICMP6_MESSAGE_TYPE_MLD2_REPORT 143
 
 //
@@ -72,13 +77,38 @@ Members:
 --*/
 
 typedef struct _ICMP6_HEADER {
-    BYTE Type;
-    BYTE Code;
+    UCHAR Type;
+    UCHAR Code;
     USHORT Checksum;
 } ICMP6_HEADER, *PICMP6_HEADER;
 
 /*++
 
+Structure Description:
+
+    This structure defines a request for ICMPv6 to configure or dismantle a
+    network address.
+
+Members:
+
+    Link - Stores a pointer to the link to which the network address belongs.
+
+    LinkAddress - Stores a pointer to the link address entry to configure or
+        dismantle.
+
+    Configure - Stores a boolean indicating if the address shall be configured
+        (TRUE) or dismantled (FALSE).
+
+--*/
+
+typedef struct _ICMP6_ADDRESS_CONFIGURATION_REQUEST {
+    PNET_LINK Link;
+    PNET_LINK_ADDRESS_ENTRY LinkAddress;
+    BOOL Configure;
+} ICMP6_ADDRESS_CONFIGURATION_REQUEST, *PICMP6_ADDRESS_CONFIGURATION_REQUEST;
+
+/*++
+
 Enumeration Description:
 
     This enumeration describes the various ICMPv6 options for the ICMPv6 socket
@@ -96,12 +126,17 @@ Values:
         multicast group. This option takes a NET_NETWORK_MULTICAST_REQUEST
         structure.
 
+    SocketIcmp6OptionConfigureAddress - Indicates a request to configure or
+        dismantle a network address. This options takes an
+        ICMP6_ADDRESS_CONFIGURATION_REQUEST structure.
+
 --*/
 
 typedef enum _SOCKET_ICMP6_OPTION {
     SocketIcmp6OptionInvalid,
     SocketIcmp6OptionJoinMulticastGroup,
-    SocketIcmp6OptionLeaveMulticastGroup
+    SocketIcmp6OptionLeaveMulticastGroup,
+    SocketIcmp6OptionConfigureAddress
 } SOCKET_ICMP6_OPTION, *PSOCKET_ICMP6_OPTION;
 
 //

+ 1 - 0
include/minoca/net/ip4.h

@@ -69,6 +69,7 @@ Author:
 #define IP4_INITIAL_TIME_TO_LIVE 63
 #define IP4_INITIAL_MULTICAST_TIME_TO_LIVE 1
 #define IP4_LINK_LOCAL_TIME_TO_LIVE 1
+#define IP4_MAX_TIME_TO_LIVE 255
 
 #define IP4_BROADCAST_ADDRESS    0xFFFFFFFF
 

+ 12 - 0
include/minoca/net/ip6.h

@@ -59,6 +59,18 @@ Author:
     (((_Ip6Address)[0] == CPU_TO_NETWORK32(IP6_LINK_LOCAL_PREFIX)) && \
      ((_Ip6Address)[1] == 0))
 
+//
+// This macro determines whether or not the given IPv6 address is a
+// solicited-node multicast address.
+//
+
+#define IP6_IS_SOLICITED_NODE_MULTICAST_ADDRESS(_Ip6Address) \
+    (((_Ip6Address)[0] == CPU_TO_NETWORK32(0xFF020000)) &&   \
+     ((_Ip6Address)[1] == 0) &&                              \
+     ((_Ip6Address)[2] == CPU_TO_NETWORK32(0x00000001)) &&   \
+     (((_Ip6Address)[3] & CPU_TO_NETWORK32(0xFF000000)) ==   \
+      CPU_TO_NETWORK32(0xFF000000)))
+
 //
 // ---------------------------------------------------------------- Definitions
 //

+ 1 - 0
include/minoca/net/netdrv.h

@@ -225,6 +225,7 @@ Author:
 #define NET_PACKET_FLAG_MULTICAST            0x00000100
 #define NET_PACKET_FLAG_ROUTER_ALERT         0x00000200
 #define NET_PACKET_FLAG_LINK_LOCAL_HOP_LIMIT 0x00000400
+#define NET_PACKET_FLAG_MAX_HOP_LIMIT        0x00000800
 
 #define NET_PACKET_FLAG_CHECKSUM_OFFLOAD_MASK \
     (NET_PACKET_FLAG_IP_CHECKSUM_OFFLOAD |    \