Browse Source

Add the Multicast Listener Discovery protocol.

IPv6 uses MLD, a sub-protocol of ICMPv6, in order to advertise multicast
group membership. MLD is quite similar to IGMP, but different enough to
deserve its own module.
Chris Stevens 6 years ago
parent
commit
0d77246b3d

+ 27 - 0
drivers/net/net80211/net80211.c

@@ -31,6 +31,7 @@ Environment:
 
 #include "net80211.h"
 #include <minoca/net/ip4.h>
+#include <minoca/net/ip6.h>
 
 //
 // ---------------------------------------------------------------- Definitions
@@ -137,6 +138,14 @@ UUID Net80211NetworkDeviceInformationUuid =
 UCHAR Net80211Ip4MulticastBase[ETHERNET_ADDRESS_SIZE] =
     {0x01, 0x00, 0x5E, 0x00, 0x00, 0x00};
 
+//
+// Stores the base MAC address for all IPv6 multicast addresses. The last four
+// bytes are taken from last four bytes of the IPv6 address.
+//
+
+UCHAR Net80211Ip6MulticastBase[ETHERNET_ADDRESS_SIZE] =
+    {0x33, 0x33, 0x00, 0x00, 0x00, 0x00};
+
 //
 // ------------------------------------------------------------------ Functions
 //
@@ -960,6 +969,7 @@ Return Value:
     ULONG Ip4AddressMask;
     PUCHAR Ip4BytePointer;
     PIP4_ADDRESS Ip4Multicast;
+    PIP6_ADDRESS Ip6Multicast;
     KSTATUS Status;
 
     BytePointer = (PUCHAR)(PhysicalAddress->Address);
@@ -1021,6 +1031,23 @@ Return Value:
             BytePointer[5] = Ip4BytePointer[3];
             break;
 
+        case NetDomainIp6:
+
+            //
+            // The IPv6 multicast MAC address is formed by taking the last four
+            // bytes of the IPv6 address and prepending them with two bytes of
+            // 0x33.
+            //
+
+            Ip6Multicast = (PIP6_ADDRESS)NetworkAddress;
+            BytePointer[0] = Net80211Ip6MulticastBase[0];
+            BytePointer[1] = Net80211Ip6MulticastBase[1];
+            BytePointer[2] = Ip6Multicast->Address[12];
+            BytePointer[3] = Ip6Multicast->Address[13];
+            BytePointer[4] = Ip6Multicast->Address[14];
+            BytePointer[5] = Ip6Multicast->Address[15];
+            break;
+
         default:
             Status = STATUS_NOT_SUPPORTED;
             break;

+ 2 - 0
drivers/net/netcore/Makefile

@@ -46,7 +46,9 @@ OBJS = addr.o            \
        ipv4/dhcp.o       \
        ipv4/igmp.o       \
        ipv4/ip4.o        \
+       ipv6/icmp6.o      \
        ipv6/ip6.o        \
+       ipv6/mld.o        \
        netlink/netlink.o \
        netlink/genctrl.o \
        netlink/generic.o \

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

@@ -43,7 +43,9 @@ function build() {
         "ipv4/dhcp.c",
         "ipv4/igmp.c",
         "ipv4/ip4.c",
+        "ipv6/icmp6.c",
         "ipv6/ip6.c",
+        "ipv6/mld.c",
         "mcast.c",
         "netcore.c",
         "netlink/netlink.c",

+ 27 - 0
drivers/net/netcore/ethernet.c

@@ -41,6 +41,7 @@ Environment:
 #include <minoca/kernel/driver.h>
 #include <minoca/net/netdrv.h>
 #include <minoca/net/ip4.h>
+#include <minoca/net/ip6.h>
 #include <minoca/kernel/acpi.h>
 #include <minoca/fw/smbios.h>
 #include "ethernet.h"
@@ -156,6 +157,14 @@ ULONG EthernetDebugFlags = 0;
 UCHAR NetEthernetIp4MulticastBase[ETHERNET_ADDRESS_SIZE] =
     {0x01, 0x00, 0x5E, 0x00, 0x00, 0x00};
 
+//
+// Stores the base MAC address for all IPv6 multicast addresses. The last four
+// bytes are taken from last four bytes of the IPv6 address.
+//
+
+UCHAR NetEthernetIp6MulticastBase[ETHERNET_ADDRESS_SIZE] =
+    {0x33, 0x33, 0x00, 0x00, 0x00, 0x00};
+
 //
 // ------------------------------------------------------------------ Functions
 //
@@ -626,6 +635,7 @@ Return Value:
     ULONG Ip4AddressMask;
     PUCHAR Ip4BytePointer;
     PIP4_ADDRESS Ip4Multicast;
+    PIP6_ADDRESS Ip6Multicast;
     KSTATUS Status;
 
     BytePointer = (PUCHAR)(PhysicalAddress->Address);
@@ -687,6 +697,23 @@ Return Value:
             BytePointer[5] = Ip4BytePointer[3];
             break;
 
+        case NetDomainIp6:
+
+            //
+            // The IPv6 multicast MAC address is formed by taking the last four
+            // bytes of the IPv6 address and prepending them with two bytes of
+            // 0x33.
+            //
+
+            Ip6Multicast = (PIP6_ADDRESS)NetworkAddress;
+            BytePointer[0] = NetEthernetIp6MulticastBase[0];
+            BytePointer[1] = NetEthernetIp6MulticastBase[1];
+            BytePointer[2] = Ip6Multicast->Address[12];
+            BytePointer[3] = Ip6Multicast->Address[13];
+            BytePointer[4] = Ip6Multicast->Address[14];
+            BytePointer[5] = Ip6Multicast->Address[15];
+            break;
+
         default:
             Status = STATUS_NOT_SUPPORTED;
             break;

+ 6 - 7
drivers/net/netcore/ipv4/ip4.c

@@ -2384,13 +2384,12 @@ Return Value:
     }
 
     RequestSize = sizeof(NET_NETWORK_MULTICAST_REQUEST);
-    Status = Protocol->Interface.GetSetInformation(
-                                           NULL,
-                                           SocketInformationIgmp,
-                                           Option,
-                                           Request,
-                                           &RequestSize,
-                                           TRUE);
+    Status = Protocol->Interface.GetSetInformation(NULL,
+                                                   SocketInformationIgmp,
+                                                   Option,
+                                                   Request,
+                                                   &RequestSize,
+                                                   TRUE);
 
     return Status;
 }

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

@@ -0,0 +1,881 @@
+/*++
+
+Copyright (c) 2017 Minoca Corp. All Rights Reserved
+
+Module Name:
+
+    icmp6.c
+
+Abstract:
+
+    This module implements support for the Internet Control Message Protocol
+    version 6, which encapsulates a range of IPv6 message types including NDP
+    and MLD.
+
+Author:
+
+    Chris Stevens 23-Aug-2017
+
+Environment:
+
+    Kernel
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// Network layer drivers are supposed to be able to stand on their own (i.e. 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>
+#include "mld.h"
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+//
+// ----------------------------------------------- Internal Function Prototypes
+//
+
+KSTATUS
+NetpIcmp6CreateSocket (
+    PNET_PROTOCOL_ENTRY ProtocolEntry,
+    PNET_NETWORK_ENTRY NetworkEntry,
+    ULONG NetworkProtocol,
+    PNET_SOCKET *NewSocket,
+    ULONG Phase
+    );
+
+VOID
+NetpIcmp6DestroySocket (
+    PNET_SOCKET Socket
+    );
+
+KSTATUS
+NetpIcmp6BindToAddress (
+    PNET_SOCKET Socket,
+    PNET_LINK Link,
+    PNETWORK_ADDRESS Address
+    );
+
+KSTATUS
+NetpIcmp6Listen (
+    PNET_SOCKET Socket
+    );
+
+KSTATUS
+NetpIcmp6Accept (
+    PNET_SOCKET Socket,
+    PIO_HANDLE *NewConnectionSocket,
+    PNETWORK_ADDRESS RemoteAddress
+    );
+
+KSTATUS
+NetpIcmp6Connect (
+    PNET_SOCKET Socket,
+    PNETWORK_ADDRESS Address
+    );
+
+KSTATUS
+NetpIcmp6Close (
+    PNET_SOCKET Socket
+    );
+
+KSTATUS
+NetpIcmp6Shutdown (
+    PNET_SOCKET Socket,
+    ULONG ShutdownType
+    );
+
+KSTATUS
+NetpIcmp6Send (
+    BOOL FromKernelMode,
+    PNET_SOCKET Socket,
+    PSOCKET_IO_PARAMETERS Parameters,
+    PIO_BUFFER IoBuffer
+    );
+
+VOID
+NetpIcmp6ProcessReceivedData (
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+KSTATUS
+NetpIcmp6ProcessReceivedSocketData (
+    PNET_SOCKET Socket,
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    );
+
+KSTATUS
+NetpIcmp6Receive (
+    BOOL FromKernelMode,
+    PNET_SOCKET Socket,
+    PSOCKET_IO_PARAMETERS Parameters,
+    PIO_BUFFER IoBuffer
+    );
+
+KSTATUS
+NetpIcmp6GetSetInformation (
+    PNET_SOCKET Socket,
+    SOCKET_INFORMATION_TYPE InformationType,
+    UINTN SocketOption,
+    PVOID Data,
+    PUINTN DataSize,
+    BOOL Set
+    );
+
+KSTATUS
+NetpIcmp6UserControl (
+    PNET_SOCKET Socket,
+    ULONG CodeNumber,
+    BOOL FromKernelMode,
+    PVOID ContextBuffer,
+    UINTN ContextBufferSize
+    );
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+NET_PROTOCOL_ENTRY NetIcmp6Protocol = {
+    {NULL, NULL},
+    NetSocketDatagram,
+    SOCKET_INTERNET_PROTOCOL_ICMP6,
+    0,
+    NULL,
+    NULL,
+    {{0}, {0}, {0}},
+    {
+        NetpIcmp6CreateSocket,
+        NetpIcmp6DestroySocket,
+        NetpIcmp6BindToAddress,
+        NetpIcmp6Listen,
+        NetpIcmp6Accept,
+        NetpIcmp6Connect,
+        NetpIcmp6Close,
+        NetpIcmp6Shutdown,
+        NetpIcmp6Send,
+        NetpIcmp6ProcessReceivedData,
+        NetpIcmp6ProcessReceivedSocketData,
+        NetpIcmp6Receive,
+        NetpIcmp6GetSetInformation,
+        NetpIcmp6UserControl
+    }
+};
+
+//
+// ------------------------------------------------------------------ Functions
+//
+
+VOID
+NetpIcmp6Initialize (
+    VOID
+    )
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for the ICMPv6 protocol.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    KSTATUS Status;
+
+    //
+    // Register the ICMPv6 socket handlers with the core networking library.
+    //
+
+    Status = NetRegisterProtocol(&NetIcmp6Protocol, NULL);
+    if (!KSUCCESS(Status)) {
+
+        ASSERT(FALSE);
+
+    }
+
+    //
+    // Initialize any sub-protocols of ICMPv6.
+    //
+
+    NetpMldInitialize();
+    return;
+}
+
+KSTATUS
+NetpIcmp6CreateSocket (
+    PNET_PROTOCOL_ENTRY ProtocolEntry,
+    PNET_NETWORK_ENTRY NetworkEntry,
+    ULONG NetworkProtocol,
+    PNET_SOCKET *NewSocket,
+    ULONG Phase
+    )
+
+/*++
+
+Routine Description:
+
+    This routine allocates resources associated with a new socket. The protocol
+    driver is responsible for allocating the structure (with additional length
+    for any of its context). The core networking library will fill in the
+    common header when this routine returns.
+
+Arguments:
+
+    ProtocolEntry - Supplies a pointer to the protocol information.
+
+    NetworkEntry - Supplies a pointer to the network information.
+
+    NetworkProtocol - Supplies the raw protocol value for this socket used on
+        the network. This value is network specific.
+
+    NewSocket - Supplies a pointer where a pointer to a newly allocated
+        socket structure will be returned. The caller is responsible for
+        allocating the socket (and potentially a larger structure for its own
+        context). The core network library will fill in the standard socket
+        structure after this routine returns. In phase 1, this will contain
+        a pointer to the socket allocated during phase 0.
+
+    Phase - Supplies the socket creation phase. Phase 0 is the allocation phase
+        and phase 1 is the advanced initialization phase, which is invoked
+        after net core is done filling out common portions of the socket
+        structure.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+VOID
+NetpIcmp6DestroySocket (
+    PNET_SOCKET Socket
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys resources associated with an open socket, officially
+    marking the end of the kernel and core networking library's knowledge of
+    this structure.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to destroy. The core networking
+        library will have already destroyed any resources inside the common
+        header, the protocol should not reach through any pointers inside the
+        socket header except the protocol and network entries.
+
+Return Value:
+
+    None. This routine is responsible for freeing the memory associated with
+    the socket structure itself.
+
+--*/
+
+{
+
+    return;
+}
+
+KSTATUS
+NetpIcmp6BindToAddress (
+    PNET_SOCKET Socket,
+    PNET_LINK Link,
+    PNETWORK_ADDRESS Address
+    )
+
+/*++
+
+Routine Description:
+
+    This routine binds the given socket to the specified network address.
+    Usually this is a no-op for the protocol, it's simply responsible for
+    passing the request down to the network layer.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to bind.
+
+    Link - Supplies an optional pointer to a link to bind to.
+
+    Address - Supplies a pointer to the address to bind the socket to.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Listen (
+    PNET_SOCKET Socket
+    )
+
+/*++
+
+Routine Description:
+
+    This routine adds a bound socket to the list of listening sockets,
+    officially allowing clients to attempt to connect to it.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to mark as listning.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Accept (
+    PNET_SOCKET Socket,
+    PIO_HANDLE *NewConnectionSocket,
+    PNETWORK_ADDRESS RemoteAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine accepts an incoming connection on a listening connection-based
+    socket.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to accept a connection from.
+
+    NewConnectionSocket - Supplies a pointer where a new socket will be
+        returned that represents the accepted connection with the remote
+        host.
+
+    RemoteAddress - Supplies a pointer where the address of the connected
+        remote host will be returned.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Connect (
+    PNET_SOCKET Socket,
+    PNETWORK_ADDRESS Address
+    )
+
+/*++
+
+Routine Description:
+
+    This routine attempts to make an outgoing connection to a server.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to use for the connection.
+
+    Address - Supplies a pointer to the address to connect to.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Close (
+    PNET_SOCKET Socket
+    )
+
+/*++
+
+Routine Description:
+
+    This routine closes a socket connection.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to shut down.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Shutdown (
+    PNET_SOCKET Socket,
+    ULONG ShutdownType
+    )
+
+/*++
+
+Routine Description:
+
+    This routine shuts down communication with a given socket.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket.
+
+    ShutdownType - Supplies the shutdown type to perform. See the
+        SOCKET_SHUTDOWN_* definitions.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Send (
+    BOOL FromKernelMode,
+    PNET_SOCKET Socket,
+    PSOCKET_IO_PARAMETERS Parameters,
+    PIO_BUFFER IoBuffer
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends the given data buffer through the network using a
+    specific protocol.
+
+Arguments:
+
+    FromKernelMode - Supplies a boolean indicating whether the request is
+        coming from kernel mode (TRUE) or user mode (FALSE).
+
+    Socket - Supplies a pointer to the socket to send the data to.
+
+    Parameters - Supplies a pointer to the socket I/O parameters. This will
+        always be a kernel mode pointer.
+
+    IoBuffer - Supplies a pointer to the I/O buffer containing the data to
+        send.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+VOID
+NetpIcmp6ProcessReceivedData (
+    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.
+
+--*/
+
+{
+
+    USHORT Checksum;
+    PICMP6_HEADER Icmp6Header;
+    PNET_PACKET_BUFFER Packet;
+    ULONG PacketSize;
+
+    //
+    // Validate the ICMPv6 header.
+    //
+
+    Packet = ReceiveContext->Packet;
+    PacketSize = Packet->FooterOffset - Packet->DataOffset;
+    if (PacketSize < sizeof(ICMP6_HEADER)) {
+        RtlDebugPrint("ICMP6: Packet length (0x%08x) less than header size "
+                      "(0x%08x)\n",
+                      PacketSize,
+                      sizeof(ICMP6_HEADER));
+
+        return;
+    }
+
+    Icmp6Header = Packet->Buffer + Packet->DataOffset;
+    Checksum = NetChecksumPseudoHeaderAndData(ReceiveContext->Network,
+                                              Icmp6Header,
+                                              PacketSize,
+                                              ReceiveContext->Source,
+                                              ReceiveContext->Destination,
+                                              SOCKET_INTERNET_PROTOCOL_ICMP6);
+
+    if (Checksum != 0) {
+        RtlDebugPrint("ICMP6: Invalid checksum 0x%04x.\n", Checksum);
+        return;
+    }
+
+    //
+    // Act according to the ICMPv6 message.
+    //
+
+    Packet->DataOffset += sizeof(ICMP6_HEADER);
+    switch (Icmp6Header->Type) {
+    case ICMP6_MESSAGE_TYPE_MLD_QUERY:
+    case ICMP6_MESSAGE_TYPE_MLD_REPORT:
+    case ICMP6_MESSAGE_TYPE_MLD_DONE:
+    case ICMP6_MESSAGE_TYPE_MLD2_REPORT:
+        NetpMldProcessReceivedData(ReceiveContext);
+        break;
+
+    default:
+        break;
+    }
+
+    return;
+}
+
+KSTATUS
+NetpIcmp6ProcessReceivedSocketData (
+    PNET_SOCKET Socket,
+    PNET_RECEIVE_CONTEXT ReceiveContext
+    )
+
+/*++
+
+Routine Description:
+
+    This routine is called for a particular socket to process a received packet
+    that was sent to it.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket that received the packet.
+
+    ReceiveContext - Supplies a pointer to the receive context that stores the
+        link, packet, network, protocol, and source and destination addresses.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6Receive (
+    BOOL FromKernelMode,
+    PNET_SOCKET Socket,
+    PSOCKET_IO_PARAMETERS Parameters,
+    PIO_BUFFER IoBuffer
+    )
+
+/*++
+
+Routine Description:
+
+    This routine is called by the user to receive data from the socket on a
+    particular protocol.
+
+Arguments:
+
+    FromKernelMode - Supplies a boolean indicating whether the request is
+        coming from kernel mode (TRUE) or user mode (FALSE).
+
+    Socket - Supplies a pointer to the socket to receive data from.
+
+    Parameters - Supplies a pointer to the socket I/O parameters.
+
+    IoBuffer - Supplies a pointer to the I/O buffer where the received data
+        will be returned.
+
+Return Value:
+
+    STATUS_SUCCESS if any bytes were read.
+
+    STATUS_TIMEOUT if the request timed out.
+
+    STATUS_BUFFER_TOO_SMALL if the incoming datagram was too large for the
+        buffer. The remainder of the datagram is discarded in this case.
+
+    Other error codes on other failures.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+}
+
+KSTATUS
+NetpIcmp6GetSetInformation (
+    PNET_SOCKET Socket,
+    SOCKET_INFORMATION_TYPE InformationType,
+    UINTN Option,
+    PVOID Data,
+    PUINTN DataSize,
+    BOOL Set
+    )
+
+/*++
+
+Routine Description:
+
+    This routine gets or sets properties of the given socket.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket to get or set information for.
+
+    InformationType - Supplies the socket information type category to which
+        specified option belongs.
+
+    Option - Supplies the option to get or set, which is specific to the
+        information type. The type of this value is generally
+        SOCKET_<information_type>_OPTION.
+
+    Data - Supplies a pointer to the data buffer where the data is either
+        returned for a get operation or given for a set operation.
+
+    DataSize - Supplies a pointer that on input constains the size of the data
+        buffer. On output, this contains the required size of the data buffer.
+
+    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
+        a set operation (TRUE).
+
+Return Value:
+
+    STATUS_SUCCESS on success.
+
+    STATUS_INVALID_PARAMETER if the information type is incorrect.
+
+    STATUS_BUFFER_TOO_SMALL if the data buffer is too small to receive the
+        requested option.
+
+    STATUS_NOT_SUPPORTED_BY_PROTOCOL if the socket option is not supported by
+        the socket.
+
+    STATUS_NOT_HANDLED if the protocol does not override the default behavior
+        for a basic socket option.
+
+--*/
+
+{
+
+    SOCKET_ICMP6_OPTION Icmp6;
+    PIP6_ADDRESS MulticastAddress;
+    PNET_NETWORK_MULTICAST_REQUEST MulticastRequest;
+    UINTN RequiredSize;
+    PVOID Source;
+    KSTATUS Status;
+
+    if (InformationType != SocketInformationIcmp6) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto Icmp6GetSetInformationEnd;
+    }
+
+    RequiredSize = 0;
+    Source = NULL;
+    Status = STATUS_SUCCESS;
+    Icmp6 = (SOCKET_ICMP6_OPTION)Option;
+    switch (Icmp6) {
+    case SocketIcmp6OptionJoinMulticastGroup:
+    case SocketIcmp6OptionLeaveMulticastGroup:
+        if (Set == FALSE) {
+            Status = STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+            break;
+        }
+
+        RequiredSize = sizeof(NET_NETWORK_MULTICAST_REQUEST);
+        if (*DataSize < RequiredSize) {
+            *DataSize = RequiredSize;
+            Status = STATUS_BUFFER_TOO_SMALL;
+            break;
+        }
+
+        MulticastRequest = (PNET_NETWORK_MULTICAST_REQUEST)Data;
+        MulticastAddress = (PIP6_ADDRESS)MulticastRequest->MulticastAddress;
+        if ((MulticastAddress->Domain != NetDomainIp6) ||
+            (!IP6_IS_MULTICAST_ADDRESS(MulticastAddress->Address))) {
+
+            Status = STATUS_INVALID_PARAMETER;
+            break;
+        }
+
+        if (Icmp6 == SocketIcmp6OptionJoinMulticastGroup) {
+            Status = NetpMldJoinMulticastGroup(MulticastRequest);
+
+        } else {
+            Status = NetpMldLeaveMulticastGroup(MulticastRequest);
+        }
+
+        break;
+
+    default:
+        Status = STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+        break;
+    }
+
+    if (!KSUCCESS(Status)) {
+        goto Icmp6GetSetInformationEnd;
+    }
+
+    //
+    // Truncate all copies for get requests down to the required size and
+    // always return the required size on set requests.
+    //
+
+    if (*DataSize > RequiredSize) {
+        *DataSize = RequiredSize;
+    }
+
+    //
+    // For get requests, copy the gathered information to the supplied data
+    // buffer.
+    //
+
+    if (Set == FALSE) {
+
+        ASSERT(Source != NULL);
+
+        RtlCopyMemory(Data, Source, *DataSize);
+
+        //
+        // If the copy truncated the data, report that the given buffer was too
+        // small. The caller can choose to ignore this if the truncated data is
+        // enough.
+        //
+
+        if (*DataSize < RequiredSize) {
+            *DataSize = RequiredSize;
+            Status = STATUS_BUFFER_TOO_SMALL;
+            goto Icmp6GetSetInformationEnd;
+        }
+    }
+
+Icmp6GetSetInformationEnd:
+    return Status;
+}
+
+KSTATUS
+NetpIcmp6UserControl (
+    PNET_SOCKET Socket,
+    ULONG CodeNumber,
+    BOOL FromKernelMode,
+    PVOID ContextBuffer,
+    UINTN ContextBufferSize
+    )
+
+/*++
+
+Routine Description:
+
+    This routine handles user control requests destined for a socket.
+
+Arguments:
+
+    Socket - Supplies a pointer to the socket.
+
+    CodeNumber - Supplies the minor code of the request.
+
+    FromKernelMode - Supplies a boolean indicating whether or not this request
+        (and the buffer associated with it) originates from user mode (FALSE)
+        or kernel mode (TRUE).
+
+    ContextBuffer - Supplies a pointer to the context buffer allocated by the
+        caller for the request.
+
+    ContextBufferSize - Supplies the size of the supplied context buffer.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    return STATUS_NOT_SUPPORTED;
+}
+
+//
+// --------------------------------------------------------- Internal Functions
+//
+

+ 71 - 6
drivers/net/netcore/ipv6/ip6.c

@@ -36,6 +36,7 @@ Environment:
 #include <minoca/kernel/driver.h>
 #include <minoca/net/netdrv.h>
 #include <minoca/net/ip6.h>
+#include <minoca/net/icmp6.h>
 
 //
 // ---------------------------------------------------------------- Definitions
@@ -219,6 +220,25 @@ NET_NETWORK_ENTRY NetIp6Network = {
     }
 };
 
+//
+// Store well-known IPv6 addresses.
+//
+
+const UCHAR NetIp6AllNodesMulticastAddress[IP6_ADDRESS_SIZE] = {
+    0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+};
+
+const UCHAR NetIp6AllRoutersMulticastAddress[IP6_ADDRESS_SIZE] = {
+    0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
+};
+
+const UCHAR NetIp6AllMld2RoutersMulticastAddress[IP6_ADDRESS_SIZE] = {
+    0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16
+};
+
 //
 // ------------------------------------------------------------------ Functions
 //
@@ -289,6 +309,7 @@ Return Value:
     PNET_LINK_ADDRESS_ENTRY AddressEntry;
     IP6_ADDRESS InitialAddress;
     PUCHAR MacAddress;
+    IP6_ADDRESS MulticastAddress;
     PNETWORK_ADDRESS PhysicalAddress;
     KSTATUS Status;
 
@@ -334,6 +355,24 @@ Return Value:
         goto Ip6InitializeLinkEnd;
     }
 
+    //
+    // Every IPv6 node should join the all-nodes multicast group.
+    //
+
+    RtlZeroMemory(&MulticastAddress, sizeof(IP6_ADDRESS));
+    MulticastAddress.Domain = NetDomainIp6;
+    RtlCopyMemory(MulticastAddress.Address,
+                  NetIp6AllNodesMulticastAddress,
+                  IP6_ADDRESS_SIZE);
+
+    Status = NetJoinLinkMulticastGroup(Link,
+                                       AddressEntry,
+                                       (PNETWORK_ADDRESS)&MulticastAddress);
+
+    if (!KSUCCESS(Status)) {
+        goto Ip6InitializeLinkEnd;
+    }
+
 Ip6InitializeLinkEnd:
     if (!KSUCCESS(Status)) {
         if (AddressEntry != NULL) {
@@ -1532,11 +1571,11 @@ Return Value:
     }
 
     Ip6Address = (PIP6_ADDRESS)Address;
-    if (IP6_IS_ANY_ADDRESS(Ip6Address) != FALSE) {
+    if (IP6_IS_ANY_ADDRESS(Ip6Address->Address) != FALSE) {
         return NetAddressAny;
     }
 
-    if (IP6_IS_MULTICAST_ADDRESS(Ip6Address) != FALSE) {
+    if (IP6_IS_MULTICAST_ADDRESS(Ip6Address->Address) != FALSE) {
         return NetAddressMulticast;
     }
 
@@ -1730,13 +1769,39 @@ Return Value:
 
 {
 
+    UINTN Option;
+    PNET_PROTOCOL_ENTRY Protocol;
+    UINTN RequestSize;
+    KSTATUS Status;
+
     //
-    // TODO: Implement MLD.
+    // This isn't going to get very far without ICMPv6 support.
     //
 
-    ASSERT(FALSE);
+    Protocol = NetGetProtocolEntry(SOCKET_INTERNET_PROTOCOL_ICMP6);
+    if (Protocol == NULL) {
+        return STATUS_NOT_SUPPORTED_BY_PROTOCOL;
+    }
 
-    return STATUS_NOT_IMPLEMENTED;
+    //
+    // ICMPv6 actually doesn't depend on a socket to join/leave a multicast
+    // group. Don't bother passing one around.
+    //
+
+    Option = SocketIcmp6OptionLeaveMulticastGroup;
+    if (Join != FALSE) {
+        Option = SocketIcmp6OptionJoinMulticastGroup;
+    }
+
+    RequestSize = sizeof(NET_NETWORK_MULTICAST_REQUEST);
+    Status = Protocol->Interface.GetSetInformation(NULL,
+                                                   SocketInformationIcmp6,
+                                                   Option,
+                                                   Request,
+                                                   &RequestSize,
+                                                   TRUE);
+
+    return Status;
 }
 
 //
@@ -1803,7 +1868,7 @@ Return Value:
     //
 
     Status = STATUS_SUCCESS;
-    if (IP6_IS_MULTICAST_ADDRESS(Ip6Address) != FALSE) {
+    if (IP6_IS_MULTICAST_ADDRESS(Ip6Address->Address) != FALSE) {
         AddressType = NetAddressMulticast;
         goto Ip6TranslateNetworkAddressEnd;
     }

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

@@ -0,0 +1,51 @@
+/*++
+
+Copyright (c) 2017 Minoca Corp. All Rights Reserved
+
+    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:
+
+    ip6addr.h
+
+Abstract:
+
+    This header contains private definitions for well-known IPv6 addresses.
+
+Author:
+
+    Chris Stevens 31-Aug-2017
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+//
+// Store well-known IPv6 addresses.
+//
+
+extern const UCHAR NetIp6AllNodesMulticastAddress[IP6_ADDRESS_SIZE];
+extern const UCHAR NetIp6AllRoutersMulticastAddress[IP6_ADDRESS_SIZE];
+extern const UCHAR NetIp6AllMld2RoutersMulticastAddress[IP6_ADDRESS_SIZE];
+
+//
+// -------------------------------------------------------- Function Prototypes
+//
+

+ 3139 - 0
drivers/net/netcore/ipv6/mld.c

@@ -0,0 +1,3139 @@
+/*++
+
+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:
+
+    mld.c
+
+Abstract:
+
+    This module implements support for the Multicast Listener Discovery
+    protocol for IPv6.
+
+Author:
+
+    Chris Stevens 29-Aug-2017
+
+Environment:
+
+    Kernel
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// Network layer drivers are supposed to be able to stand on their own (i.e. be
+// able to be implemented outside the core net library). For the builtin ones,
+// 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 MLD 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"
+
+//
+// --------------------------------------------------------------------- Macros
+//
+
+//
+// This macro converts MLDv2 response codes to actual time values.
+//
+
+#define MLD_CONVERT_RESPONSE_CODE_TO_TIME(_ResponseCode) \
+    (((_ResponseCode) < 32768) ?                         \
+     (_ResponseCode) :                                   \
+     ((((_ResponseCode) & 0x0FFF) | 0x1000) <<           \
+      ((((_ResponseCode) >> 12) & 0x7) + 3)))
+
+//
+// This macro converts MLDv2 query interval codes to actual time values.
+//
+
+#define MLD_CONVERT_INTERVAL_CODE_TO_TIME(_IntervalCode) \
+    (((_IntervalCode) < 128) ?                           \
+     (_IntervalCode) :                                   \
+     ((((_IntervalCode) & 0x0F) | 0x10) <<               \
+      ((((_IntervalCode) >> 4) & 0x7) + 3)))
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// Define the allocation tag used by MLD.
+//
+
+#define MLD_ALLOCATION_TAG 0x21646C4D // '!dlM'
+
+//
+// Define the size of the MLD IPv6 headers. Each packet should include a
+// hop-by-hop extension header with a router alert option and a Pad-N option
+// of size 0.
+//
+
+#define MLD_IP6_HEADER_SIZE                   \
+    (sizeof(IP6_HEADER) +                     \
+     (sizeof(IP6_EXTENSION_HEADER) +          \
+      (sizeof(IP6_OPTION) + sizeof(USHORT)) + \
+      (sizeof(IP6_OPTION))))
+
+//
+// All MLD packets should go out with an IPv6 hop limit of 1.
+//
+
+#define MLD_IP6_HOP_LIMIT 1
+
+//
+// Define the conversion between query response time units (milliseconds)
+// and microseconds.
+//
+
+#define MLD_MICROSECONDS_PER_QUERY_TIME_UNIT MICROSECONDS_PER_MILLISECOND
+
+//
+// Define the maximum number of address records that can be included in each
+// report.
+//
+
+#define MLD_MAX_ADDRESS_RECORD_COUNT MAX_USHORT
+
+//
+// Define the MLD address record types.
+//
+
+#define MLD_ADDRESS_RECORD_TYPE_MODE_IS_INCLUDE 1
+#define MLD_ADDRESS_RECORD_TYPE_MODE_IS_EXCLUDE 2
+#define MLD_ADDRESS_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE 3
+#define MLD_ADDRESS_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE 4
+#define MLD_ADDRESS_RECORD_TYPE_ALLOW_NEW_SOURCES 5
+#define MLD_ADDRESS_RECORD_TYPE_BLOCK_OLD_SOURCES 6
+
+//
+// Define the MLDv2 query message flag bits.
+//
+
+#define MLD_QUERY_FLAG_SUPPRESS_ROUTER_PROCESSING 0x08
+#define MLD_QUERY_FLAG_ROBUSTNESS_MASK            0x07
+#define MLD_QUERY_FLAG_ROBUSTNESS_SHIFT           0
+
+//
+// Define the required number of compatibility modes.
+//
+
+#define MLD_COMPATIBILITY_MODE_COUNT 1
+
+//
+// Define the default robustness variable.
+//
+
+#define MLD_DEFAULT_ROBUSTNESS_VARIABLE 2
+
+//
+// Define the default query interval, in seconds.
+//
+
+#define MLD_DEFAULT_QUERY_INTERVAL 125
+
+//
+// Define the default query response interval, in milliseconds.
+//
+
+#define MLD_DEFAULT_MAX_RESPONSE_TIME 10000
+
+//
+// Define the default unsolicited report interval in milliseconds
+//
+
+#define MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL 1000
+
+//
+// Define the set of multicast group flags.
+//
+
+#define MLD_MULTICAST_GROUP_FLAG_LAST_REPORT  0x00000001
+#define MLD_MULTICAST_GROUP_FLAG_STATE_CHANGE 0x00000002
+#define MLD_MULTICAST_GROUP_FLAG_LEAVE_SENT   0x00000004
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+typedef enum _MLD_VERSION {
+    MldVersion1,
+    MldVersion2
+} MLD_VERSION, *PMLD_VERSION;
+
+/*++
+
+Structure Description:
+
+    This structures defines the MLD message format.
+
+Members:
+
+    MaxResponseCode - Stores the encoded maximum allowed delay,
+        in milliseconds, before a node must send a report message in response
+        to a query message. This should be set to zero and ignored for
+        non-query messages.
+
+    Reserved - Stores 16-bites of reserved space.
+
+    MulticastAddress - Stores the IPv6 multicast address being queried by
+        address-specific queries, the address being listened to by report
+        senders, and the address no longer being listened to by done messages
+        senders. This should be 0 for general query messages.
+
+--*/
+
+typedef struct _MLD_MESSAGE {
+    ICMP6_HEADER Header;
+    USHORT MaxResponseCode;
+    USHORT Reserved;
+    UCHAR MulticastAddress[IP6_ADDRESS_SIZE];
+} PACKED MLD_MESSAGE, *PMLD_MESSAGE;
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLDv2 query message.
+
+Members:
+
+    Message - Stores the base MLD message information, compatible with MLDv1.
+
+    Flags - Stores a bitmask of MLDv2 query flags. See MLD_QUERY_FLAG_* for
+        definitions.
+
+    QueryIntervalCode - Stores the encoded query interval of the router.
+
+    SourceAddressCount - Stores the number of source address entries that
+        immediately follow this structure.
+
+--*/
+
+typedef struct _MLD2_QUERY {
+    MLD_MESSAGE Message;
+    UCHAR Flags;
+    UCHAR QueryIntervalCode;
+    USHORT SourceAddressCount;
+} PACKED MLD2_QUERY, *PMLD2_QUERY;
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLDv2 report message.
+
+Members:
+
+    Header - Stores the ICMPv6 message header.
+
+    Reserved - Stores 16-bits of reserved data.
+
+    AddressRecordCount - Stores the number of multicast address records stored
+        in the array that begins immediately after this structure.
+
+--*/
+
+typedef struct _MLD2_REPORT {
+    ICMP6_HEADER Header;
+    USHORT Reserved;
+    USHORT AddressRecordCount;
+} PACKED MLD2_REPORT, *PMLD2_REPORT;
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLDv2 multicast address record.
+
+Members:
+
+    Type - Stores the multicast address record type.
+
+    DataLength - Stores the length of auxiliary data, in 32-bit words, that
+        starts at the end of the source address array.
+
+    SourceAddressCount - Stores the number of source address entries in the
+        array that starts at the end of this structure.
+
+    MulticastAddress - Stores the multicast address of the record.
+
+--*/
+
+typedef struct _MLD2_ADDRESS_RECORD {
+    UCHAR Type;
+    UCHAR DataLength;
+    USHORT SourceAddressCount;
+    UCHAR MulticastAddress[IP6_ADDRESS_SIZE];
+} PACKED MLD2_ADDRESS_RECORD, *PMLD2_ADDRESS_RECORD;
+
+/*++
+
+Structure Description:
+
+    This structure defines an generic MLD timer that kicks off a DPC, which
+    then queues a work item.
+
+Members:
+
+    Timer - Stores a pointer to the internal timer.
+
+    Dpc - Stores a pointer to the DPC that executes when the timer expires.
+
+    WorkItem - Stores a pointer to the work item that is scheduled by the DPC.
+
+--*/
+
+typedef struct _MLD_TIMER {
+    PKTIMER Timer;
+    PDPC Dpc;
+    PWORK_ITEM WorkItem;
+} MLD_TIMER, *PMLD_TIMER;
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLD link.
+
+Members:
+
+    Node - Stores the link's entry into the global tree of MLD links.
+
+    ReferenceCount - Stores the reference count on the structure.
+
+    Link - Stores a pointer to the network link to which this MLD link is
+        bound.
+
+    LinkAddress - Stores a pointer to the network link address entry with which
+        the MLD link is associated.
+
+    MaxPacketSize - Stores the maximum MLD packet size that can be sent over
+        the link.
+
+    RobustnessVariable - Stores the multicast router's robustness variable.
+
+    QueryInterval - Stores the multicat router's query interval, in seconds.
+
+    MaxResponseTime - Stores the maximum response time for a MLD report, in
+        milliseconds.
+
+    Lock - Stores a queued lock that protects the MLD link.
+
+    CompatibilityMode - Stores the current compatibility mode of the MLD link.
+        This is based on the type of query messages received on the network.
+
+    CompatibilityTimer - Stores an array of timers for each of the older
+        versions of MLD that must be supported.
+
+    ReportTimer - Stores the report timer used for responding to generic
+        queries.
+
+    ReportableGroupCount - Stores the number of multicast groups that are
+        associated with the link and should be reported in a total link report.
+
+    MulticastGroupList - Stores the list of the multicast group structures
+        associated with the link.
+
+--*/
+
+typedef struct _MLD_LINK {
+    RED_BLACK_TREE_NODE Node;
+    volatile ULONG ReferenceCount;
+    PNET_LINK Link;
+    PNET_LINK_ADDRESS_ENTRY LinkAddress;
+    ULONG MaxPacketSize;
+    ULONG RobustnessVariable;
+    ULONG QueryInterval;
+    ULONG MaxResponseTime;
+    PQUEUED_LOCK Lock;
+    volatile MLD_VERSION CompatibilityMode;
+    MLD_TIMER CompatibilityTimer[MLD_COMPATIBILITY_MODE_COUNT];
+    MLD_TIMER ReportTimer;
+    ULONG ReportableGroupCount;
+    LIST_ENTRY MulticastGroupList;
+} MLD_LINK, *PMLD_LINK;
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLD multicast group.
+
+Members:
+
+    ListEntry - Stores the group's entry into its parent's list of multicast
+        groups.
+
+    ReferenceCount - Stores the reference count on the structure.
+
+    SendCount - Stores the number of pending report or leave messages to be
+        sending. This number should always be less than or equal to the
+        robustness value. Updates are protected by the IGMP link's queued lock.
+
+    Flags - Stores a bitmask of multicast group flags. See
+        MLD_MULTICAST_GROUP_FLAG_* for definitions. Updates are protected by
+        the IGMP link's queued lock.
+
+    JoinCount - Stores the number of times a join request has been made for
+        this multicast group. This is protected by the MLD link's queued lock.
+
+    Address - Stores the IPv6 multicast address of the group.
+
+    MldLink - Stores a pointer to the MLD link to which this group belongs.
+
+    Timer - Stores the timer used to schedule delayed and repeated MLD report
+        and leave messages.
+
+--*/
+
+typedef struct _MLD_MULTICAST_GROUP {
+    LIST_ENTRY ListEntry;
+    volatile ULONG ReferenceCount;
+    ULONG SendCount;
+    ULONG Flags;
+    ULONG JoinCount;
+    UCHAR Address[IP6_ADDRESS_SIZE];
+    PMLD_LINK MldLink;
+    MLD_TIMER Timer;
+} MLD_MULTICAST_GROUP, *PMLD_MULTICAST_GROUP;
+
+//
+// ----------------------------------------------- Internal Function Prototypes
+//
+
+VOID
+NetpMldProcessQuery (
+    PMLD_LINK Link,
+    PNET_PACKET_BUFFER Packet,
+    PNETWORK_ADDRESS SourceAddress,
+    PNETWORK_ADDRESS DestinationAddress
+    );
+
+VOID
+NetpMldProcessReport (
+    PMLD_LINK Link,
+    PNET_PACKET_BUFFER Packet,
+    PNETWORK_ADDRESS SourceAddress,
+    PNETWORK_ADDRESS DestinationAddress
+    );
+
+VOID
+NetpMldQueueReportTimer (
+    PMLD_TIMER ReportTimer,
+    ULONGLONG StartTime,
+    ULONG MaxResponseTime
+    );
+
+VOID
+NetpMldTimerDpcRoutine (
+    PDPC Dpc
+    );
+
+VOID
+NetpMldGroupTimeoutWorker (
+    PVOID Parameter
+    );
+
+VOID
+NetpMldLinkReportTimeoutWorker (
+    PVOID Parameter
+    );
+
+VOID
+NetpMldLinkCompatibilityTimeoutWorker (
+    PVOID Parameter
+    );
+
+VOID
+NetpMldQueueCompatibilityTimer (
+    PMLD_LINK MldLink,
+    MLD_VERSION CompatibilityMode
+    );
+
+VOID
+NetpMldUpdateCompatibilityMode (
+    PMLD_LINK MldLink
+    );
+
+VOID
+NetpMldSendGroupReport (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+VOID
+NetpMldSendLinkReport (
+    PMLD_LINK Link
+    );
+
+VOID
+NetpMldSendPackets (
+    PMLD_LINK MldLink,
+    PNETWORK_ADDRESS Destination,
+    PNET_PACKET_LIST PacketList,
+    UCHAR Type
+    );
+
+VOID
+NetpMldSendGroupLeave (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+PMLD_LINK
+NetpMldCreateOrLookupLink (
+    PNET_LINK Link,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress
+    );
+
+VOID
+NetpMldDestroyLink (
+    PMLD_LINK MldLink
+    );
+
+PMLD_LINK
+NetpMldLookupLink (
+    PNET_LINK Link
+    );
+
+VOID
+NetpMldLinkAddReference (
+    PMLD_LINK MldLink
+    );
+
+VOID
+NetpMldLinkReleaseReference (
+    PMLD_LINK MldLink
+    );
+
+COMPARISON_RESULT
+NetpMldCompareLinkEntries (
+    PRED_BLACK_TREE Tree,
+    PRED_BLACK_TREE_NODE FirstNode,
+    PRED_BLACK_TREE_NODE SecondNode
+    );
+
+PMLD_MULTICAST_GROUP
+NetpMldCreateGroup (
+    PMLD_LINK MldLink,
+    PIP6_ADDRESS GroupAddress
+    );
+
+VOID
+NetpMldDestroyGroup (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+PMLD_MULTICAST_GROUP
+NetpMldLookupGroup (
+    PMLD_LINK IgmpLink,
+    PIP6_ADDRESS GroupAddress
+    );
+
+VOID
+NetpMldGroupAddReference (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+VOID
+NetpMldGroupReleaseReference (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+KSTATUS
+NetpMldInitializeTimer (
+    PMLD_TIMER Timer,
+    PWORK_ITEM_ROUTINE WorkRoutine,
+    PVOID WorkParameter
+    );
+
+VOID
+NetpMldDestroyTimer (
+    PMLD_TIMER Timer
+    );
+
+BOOL
+NetpMldIsReportableGroup (
+    PMLD_MULTICAST_GROUP Group
+    );
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+//
+// Stores a global tree of net links that are signed up for multicast groups
+// via MLD.
+//
+
+RED_BLACK_TREE NetMldLinkTree;
+PSHARED_EXCLUSIVE_LOCK NetMldLinkLock;
+
+//
+// ------------------------------------------------------------------ Functions
+//
+
+VOID
+NetpMldInitialize (
+    VOID
+    )
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for the MLD protocol.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    RtlRedBlackTreeInitialize(&NetMldLinkTree, 0, NetpMldCompareLinkEntries);
+    NetMldLinkLock = KeCreateSharedExclusiveLock();
+    if (NetMldLinkLock == NULL) {
+
+        ASSERT(FALSE);
+
+        return;
+    }
+
+    return;
+}
+
+VOID
+NetpMldProcessReceivedData (
+    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;
+    PMLD_LINK MldLink;
+    PNET_PACKET_BUFFER Packet;
+    PIP6_ADDRESS Source;
+
+    ASSERT(KeGetRunLevel() == RunLevelLow);
+
+    //
+    // All messages should come from link-local source addresses.
+    //
+
+    Source = (PIP6_ADDRESS)ReceiveContext->Source;
+    if (IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(Source->Address) == FALSE) {
+        return;
+    }
+
+    //
+    // Do nothing if this link is not registered with MLD. The packet is likely
+    // old.
+    //
+
+    MldLink = NetpMldLookupLink(ReceiveContext->Link);
+    if (MldLink == NULL) {
+        goto MldProcessReceivedDataEnd;
+    }
+
+    //
+    // Handle the MLD packet based on the ICMPv6 type field. ICMPv6 already
+    // validated the header and its checksum.
+    //
+
+    Packet = ReceiveContext->Packet;
+    Header = (PICMP6_HEADER)(Packet->Buffer + Packet->DataOffset);
+    switch (Header->Type) {
+    case ICMP6_MESSAGE_TYPE_MLD_QUERY:
+        NetpMldProcessQuery(MldLink,
+                            Packet,
+                            ReceiveContext->Source,
+                            ReceiveContext->Destination);
+
+        break;
+
+    case ICMP6_MESSAGE_TYPE_MLD_REPORT:
+    case ICMP6_MESSAGE_TYPE_MLD2_REPORT:
+        NetpMldProcessReport(MldLink,
+                             Packet,
+                             ReceiveContext->Source,
+                             ReceiveContext->Destination);
+
+        break;
+
+    //
+    // A done message should only be handled by a router.
+    //
+
+    case ICMP6_MESSAGE_TYPE_MLD_DONE:
+        break;
+
+    default:
+        break;
+    }
+
+MldProcessReceivedDataEnd:
+    if (MldLink != NULL) {
+        NetpMldLinkReleaseReference(MldLink);
+    }
+
+    return;
+}
+
+KSTATUS
+NetpMldJoinMulticastGroup (
+    PNET_NETWORK_MULTICAST_REQUEST Request
+    )
+
+/*++
+
+Routine Description:
+
+    This routine joins the multicast group on the network link provided in the
+    request. If this is the first request to join the supplied multicast group
+    on the specified link, then an MLD report is sent out over the network.
+
+Arguments:
+
+    Request - Supplies a pointer to a multicast request, which includes the
+        address of the group to join and the network link on which to join the
+        group.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    PMLD_MULTICAST_GROUP Group;
+    PIP6_ADDRESS GroupAddress;
+    BOOL LinkLockHeld;
+    PMLD_LINK MldLink;
+    PMLD_MULTICAST_GROUP NewGroup;
+    KSTATUS Status;
+
+    Group = NULL;
+    LinkLockHeld = FALSE;
+    GroupAddress = (PIP6_ADDRESS)Request->MulticastAddress;
+    NewGroup = NULL;
+
+    //
+    // Test to see if there is an MLD link for the given network link, creating
+    // one if the lookup fails.
+    //
+
+    MldLink = NetpMldLookupLink(Request->Link);
+    if (MldLink == NULL) {
+        MldLink = NetpMldCreateOrLookupLink(Request->Link,
+                                            Request->LinkAddress);
+
+        if (MldLink == NULL) {
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto JoinMulticastGroupEnd;
+        }
+    }
+
+    //
+    // Search the MLD link for the multicast group. If a matching group is not
+    // found then release the lock, allocate a group and search again. If the
+    // group is still not found, add the newly allocated group.
+    //
+
+    Status = STATUS_SUCCESS;
+    while (TRUE) {
+        KeAcquireQueuedLock(MldLink->Lock);
+        LinkLockHeld = TRUE;
+        Group = NetpMldLookupGroup(MldLink, GroupAddress);
+        if (Group != NULL) {
+            Group->JoinCount += 1;
+            goto JoinMulticastGroupEnd;
+        }
+
+        if (NewGroup == NULL) {
+            KeReleaseQueuedLock(MldLink->Lock);
+            LinkLockHeld = FALSE;
+            NewGroup = NetpMldCreateGroup(MldLink, GroupAddress);
+            if (NewGroup == NULL) {
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto JoinMulticastGroupEnd;
+            }
+
+            continue;
+        }
+
+        //
+        // Add the newly allocated group to the link's list.
+        //
+
+        INSERT_BEFORE(&(NewGroup->ListEntry), &(MldLink->MulticastGroupList));
+        if (NetpMldIsReportableGroup(NewGroup) != FALSE) {
+            MldLink->ReportableGroupCount += 1;
+        }
+
+        break;
+    }
+
+    //
+    // Initialize the send count to the robustness variable. This will cause
+    // multiple join messages to be sent, up to the robustness count.
+    //
+
+    NewGroup->SendCount = MldLink->RobustnessVariable;
+
+    //
+    // An initial join sends state change messages and at least one message
+    // will be sent, so start the group as the last reporter.
+    //
+
+    NewGroup->Flags |= MLD_MULTICAST_GROUP_FLAG_STATE_CHANGE |
+                       MLD_MULTICAST_GROUP_FLAG_LAST_REPORT;
+
+    //
+    // Take an extra reference on the new group so that it is not destroyed
+    // while sending the report. Once the lock is released, a leave request
+    // could run through and attempt to take it down.
+    //
+
+    NetpMldGroupAddReference(NewGroup);
+    KeReleaseQueuedLock(MldLink->Lock);
+    LinkLockHeld = FALSE;
+
+    //
+    // Actually send out the group's join MLD state change messages.
+    //
+
+    NetpMldSendGroupReport(NewGroup);
+
+JoinMulticastGroupEnd:
+    if (LinkLockHeld != FALSE) {
+        KeReleaseQueuedLock(MldLink->Lock);
+    }
+
+    if (MldLink != NULL) {
+        NetpMldLinkReleaseReference(MldLink);
+    }
+
+    if (NewGroup != NULL) {
+        NetpMldGroupReleaseReference(NewGroup);
+    }
+
+    if (Group != NULL) {
+        NetpMldGroupReleaseReference(Group);
+    }
+
+    return Status;
+}
+
+KSTATUS
+NetpMldLeaveMulticastGroup (
+    PNET_NETWORK_MULTICAST_REQUEST Request
+    )
+
+/*++
+
+Routine Description:
+
+    This routine removes the local system from a multicast group. If this is
+    the last request to leave a multicast group on the link, then a MLD leave
+    message is sent out over the network.
+
+Arguments:
+
+    Request - Supplies a pointer to a multicast request, which includes the
+        address of the group to leave and the network link that has previously
+        joined the group.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    PMLD_MULTICAST_GROUP Group;
+    BOOL LinkLockHeld;
+    BOOL LinkUp;
+    PMLD_LINK MldLink;
+    PIP6_ADDRESS MulticastAddress;
+    KSTATUS Status;
+
+    Group = NULL;
+    MldLink = NULL;
+    LinkLockHeld = FALSE;
+    MulticastAddress = (PIP6_ADDRESS)Request->MulticastAddress;
+
+    //
+    // Now see if there is an MLD link for the given network link.
+    //
+
+    MldLink = NetpMldLookupLink(Request->Link);
+    if (MldLink == NULL) {
+        Status = STATUS_INVALID_ADDRESS;
+        goto LeaveMulticastGroupEnd;
+    }
+
+    //
+    // Search the MLD link for the multicast group. If a matching group is not
+    // found then the request fails.
+    //
+
+    KeAcquireQueuedLock(MldLink->Lock);
+    LinkLockHeld = TRUE;
+    Group = NetpMldLookupGroup(MldLink, MulticastAddress);
+    if (Group == NULL) {
+        Status = STATUS_INVALID_ADDRESS;
+        goto LeaveMulticastGroupEnd;
+    }
+
+    //
+    // If this is not the last leave request for the group, the call is
+    // successful, but takes no further action. The link remains joined to the
+    // multicast group.
+    //
+
+    Group->JoinCount -= 1;
+    if (Group->JoinCount != 0) {
+        goto LeaveMulticastGroupEnd;
+    }
+
+    //
+    // Otherwise it's time for the group to go.
+    //
+
+    LIST_REMOVE(&(Group->ListEntry));
+    Group->ListEntry.Next = NULL;
+    if (NetpMldIsReportableGroup(Group) != FALSE) {
+        MldLink->ReportableGroupCount -= 1;
+    }
+
+    //
+    // The number of leave messages sent is dictated by the robustness variable.
+    //
+
+    Group->SendCount = MldLink->RobustnessVariable;
+
+    //
+    // Leave messages are state change messages.
+    //
+
+    Group->Flags |= MLD_MULTICAST_GROUP_FLAG_STATE_CHANGE;
+
+    //
+    // Release the lock and flush out any reports that may be in the works.
+    //
+
+    KeReleaseQueuedLock(MldLink->Lock);
+    LinkLockHeld = FALSE;
+    KeCancelTimer(Group->Timer.Timer);
+    KeFlushDpc(Group->Timer.Dpc);
+    KeCancelWorkItem(Group->Timer.WorkItem);
+    KeFlushWorkItem(Group->Timer.WorkItem);
+
+    //
+    // The send count should not have been modified.
+    //
+
+    ASSERT(Group->SendCount == MldLink->RobustnessVariable);
+
+    //
+    // If the link is up, start sending leave messages, up to the robustness
+    // count. The group's initial reference will be released after the last
+    // leave message is sent.
+    //
+
+    NetGetLinkState(MldLink->Link, &LinkUp, NULL);
+    if (LinkUp != FALSE) {
+        NetpMldSendGroupLeave(Group);
+
+    //
+    // Otherwise don't bother with the leave messages and just destroy the
+    // group immediately.
+    //
+
+    } else {
+        NetpMldGroupReleaseReference(Group);
+    }
+
+LeaveMulticastGroupEnd:
+    if (LinkLockHeld != FALSE) {
+        KeReleaseQueuedLock(MldLink->Lock);
+    }
+
+    if (MldLink != NULL) {
+        NetpMldLinkReleaseReference(MldLink);
+    }
+
+    if (Group != NULL) {
+        NetpMldGroupReleaseReference(Group);
+    }
+
+    return Status;
+}
+
+//
+// --------------------------------------------------------- Internal Functions
+//
+
+VOID
+NetpMldProcessQuery (
+    PMLD_LINK MldLink,
+    PNET_PACKET_BUFFER Packet,
+    PNETWORK_ADDRESS SourceAddress,
+    PNETWORK_ADDRESS DestinationAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine processes an MLD query message.
+
+    In host mode, this generates a report for each multicast group to which the
+    receiving link belongs.
+
+    In router mode, a query message indicates that there is another multicast
+    router on the local network. If this link has a higher IP address than the
+    sender, this link will not send queries until the "other querier present
+    interval" expires. Router mode is not currently supported.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link that received the packet.
+
+    Packet - Supplies a pointer to a structure describing the incoming packet.
+        This structure may be used as a scratch space while this routine
+        executes and the packet travels up the stack, but will not be accessed
+        after this routine returns.
+
+    SourceAddress - Supplies a pointer to the source (remote) address that the
+        packet originated from. This memory will not be referenced once the
+        function returns, it can be stack allocated.
+
+    DestinationAddress - Supplies a pointer to the destination (local) address
+        that the packet is heading to. This memory will not be referenced once
+        the function returns, it can be stack allocated.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PLIST_ENTRY CurrentEntry;
+    ULONGLONG CurrentTime;
+    PIP6_ADDRESS Destination;
+    BOOL Equal;
+    BOOL GeneralQuery;
+    PMLD_MULTICAST_GROUP Group;
+    ULONG Length;
+    UCHAR MaxResponseCode;
+    ULONG MaxResponseTime;
+    PMLD_MESSAGE Query;
+    ULONG QueryInterval;
+    PMLD2_QUERY QueryV2;
+    ULONG RobustnessVariable;
+    MLD_VERSION Version;
+
+    Destination = (PIP6_ADDRESS)DestinationAddress;
+
+    //
+    // Determine which version of query message was received. An 8 octet long
+    // message with a max response code of 0 is an MLDv1 query message. An 8
+    // octet long message with a non-zero max response code is an MLDv2 query
+    // message. A message with a length greater than or equal to 12 octets is
+    // an MLDv3 query message. Any other message must be ignored.
+    //
+
+    Query = (PMLD_MESSAGE)(Packet->Buffer + Packet->DataOffset);
+    Length = Packet->FooterOffset - Packet->DataOffset;
+    MaxResponseCode = Query->Header.Code;
+    Version = MldVersion2;
+    if (Length == sizeof(MLD_MESSAGE)) {
+        Version = MldVersion1;
+        NetpMldQueueCompatibilityTimer(MldLink, Version);
+
+    } else if (Length >= sizeof(MLD2_QUERY)) {
+        QueryV2 = (PMLD2_QUERY)Query;
+        QueryInterval = MLD_CONVERT_INTERVAL_CODE_TO_TIME(
+                                                   QueryV2->QueryIntervalCode);
+
+        RobustnessVariable = (QueryV2->Flags &
+                              MLD_QUERY_FLAG_ROBUSTNESS_MASK) >>
+                             MLD_QUERY_FLAG_ROBUSTNESS_SHIFT;
+
+        //
+        // Update the query interval and robustness variable if they are
+        // non-zero.
+        //
+
+        if (QueryInterval != 0) {
+            MldLink->QueryInterval = QueryInterval;
+        }
+
+        if (RobustnessVariable != 0) {
+            MldLink->RobustnessVariable = RobustnessVariable;
+        }
+
+    } else {
+        return;
+    }
+
+    //
+    // Version 2 queries with a hop limit greater than 1 or without the
+    // router-alert option should be ignored for security reasons.
+    //
+
+    if (Version == MldVersion2) {
+
+        //
+        // TODO: MLD needs to get the IPv6 options.
+        //
+
+    }
+
+    //
+    // All general queries not sent to the all-nodes multicast address
+    // (FF02::1) should be ignored for security reasons.
+    //
+
+    GeneralQuery = IP6_IS_ANY_ADDRESS(Query->MulticastAddress);
+    if (GeneralQuery != FALSE) {
+        Equal = RtlCompareMemory(Destination->Address,
+                                 NetIp6AllNodesMulticastAddress,
+                                 IP6_ADDRESS_SIZE);
+
+        if (Equal == FALSE) {
+            return;
+        }
+    }
+
+    //
+    // Ignore queries that target the all-nodes multicast address. No reports
+    // are supposed to be sent for the all systems address, making a query
+    // quite mysterious.
+    //
+
+    Equal = RtlCompareMemory(Query->MulticastAddress,
+                             NetIp6AllNodesMulticastAddress,
+                             IP6_ADDRESS_SIZE);
+
+    if (Equal != FALSE) {
+        return;
+    }
+
+    //
+    // Calculate the maximum response time. For query messages, the time unit
+    // is in milliseconds.
+    //
+
+    MaxResponseTime = MLD_CONVERT_RESPONSE_CODE_TO_TIME(MaxResponseCode);
+
+    //
+    // The reports are not sent immediately, but delayed based on the max
+    // response code.
+    //
+
+    KeAcquireQueuedLock(MldLink->Lock);
+
+    //
+    // Always save the max response time.
+    //
+
+    MldLink->MaxResponseTime = MaxResponseTime;
+
+    //
+    // If the host is operating in MLDv2 mode and this is a general query, set
+    // the global report timer. MLDv2 can send one report that includes
+    // information for all of the host's multicast memberships.
+    //
+
+    CurrentTime = KeGetRecentTimeCounter();
+    if ((MldLink->CompatibilityMode == MldVersion2) &&
+        (GeneralQuery != FALSE)) {
+
+        NetpMldQueueReportTimer(&(MldLink->ReportTimer),
+                                 CurrentTime,
+                                 MaxResponseTime);
+
+    //
+    // Otherwise, iterate over the list of multicast groups to which this
+    // link subscribes and update the timer for each group that matches the
+    // query's group address - or all groups if it is a general query.
+    //
+
+    } else {
+        CurrentEntry = MldLink->MulticastGroupList.Next;
+        while (CurrentEntry != &(MldLink->MulticastGroupList)) {
+            Group = LIST_VALUE(CurrentEntry, MLD_MULTICAST_GROUP, ListEntry);
+            Equal = FALSE;
+            if (GeneralQuery == FALSE) {
+                Equal = RtlCompareMemory(Query->MulticastAddress,
+                                         Group->Address,
+                                         IP6_ADDRESS_SIZE);
+
+            }
+
+            if ((GeneralQuery != FALSE) || (Equal != FALSE)) {
+                Group->Flags &= ~MLD_MULTICAST_GROUP_FLAG_STATE_CHANGE;
+                if (Group->SendCount == 0) {
+                    Group->SendCount = 1;
+                }
+
+                NetpMldQueueReportTimer(&(Group->Timer),
+                                         CurrentTime,
+                                         MaxResponseTime);
+            }
+
+            CurrentEntry = CurrentEntry->Next;
+        }
+    }
+
+    KeReleaseQueuedLock(MldLink->Lock);
+    return;
+}
+
+VOID
+NetpMldProcessReport (
+    PMLD_LINK MldLink,
+    PNET_PACKET_BUFFER Packet,
+    PNETWORK_ADDRESS SourceAddress,
+    PNETWORK_ADDRESS DestinationAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine processes an MLD report message.
+
+    In host mode, this cancels any pending report messages for the reported
+    multicast group. A router only needs to receive one report per multicast
+    group on the local physical network. It does not need to know which
+    specific hosts are subcribed to a group, just that at least one host is
+    subscribed to a group.
+
+    In router mode, a report should enabling forwarding packets destined for
+    the reported multicast group. Router mode is not currently supported.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link that received the packet.
+
+    Packet - Supplies a pointer to a structure describing the incoming packet.
+        This structure may be used as a scratch space while this routine
+        executes and the packet travels up the stack, but will not be accessed
+        after this routine returns.
+
+    SourceAddress - Supplies a pointer to the source (remote) address that the
+        packet originated from. This memory will not be referenced once the
+        function returns, it can be stack allocated.
+
+    DestinationAddress - Supplies a pointer to the destination (local) address
+        that the packet is heading to. This memory will not be referenced once
+        the function returns, it can be stack allocated.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PLIST_ENTRY CurrentEntry;
+    PIP6_ADDRESS Destination;
+    BOOL Equal;
+    PMLD_MULTICAST_GROUP Group;
+    ULONG Length;
+    PMLD_MESSAGE Report;
+
+    //
+    // MLDv2 reports are always ignored by hosts.
+    //
+
+    Report = (PMLD_MESSAGE)(Packet->Buffer + Packet->DataOffset);
+    Length = Packet->FooterOffset - Packet->DataOffset;
+    if (Length != sizeof(MLD_MESSAGE)) {
+        return;
+    }
+
+    //
+    // Version 2 reports without the router-alert option and a hop limit of 1
+    // should be ignored for security reasons.
+    //
+
+    if (Report->Header.Type == ICMP6_MESSAGE_TYPE_MLD2_REPORT) {
+
+        //
+        // TODO: MLD needs to get the IPv6 options.
+        //
+
+    }
+
+    //
+    // The report should have been sent to the multicast group it was reporting
+    // on.
+    //
+
+    Destination = (PIP6_ADDRESS)DestinationAddress;
+    Equal = RtlCompareMemory(Destination->Address,
+                             Report->MulticastAddress,
+                             IP6_ADDRESS_SIZE);
+
+    if ((Equal == FALSE) ||
+        (IP6_IS_ANY_ADDRESS(Destination->Address) != FALSE)) {
+
+        return;
+    }
+
+    //
+    // If this MLD link belongs to the multicast group, cancel any pending
+    // reports and record that this link was not the last to send a report.
+    //
+
+    KeAcquireQueuedLock(MldLink->Lock);
+    CurrentEntry = MldLink->MulticastGroupList.Next;
+    while (CurrentEntry != &(MldLink->MulticastGroupList)) {
+        Group = LIST_VALUE(CurrentEntry, MLD_MULTICAST_GROUP, ListEntry);
+        Equal = RtlCompareMemory(Report->MulticastAddress,
+                                 Group->Address,
+                                 IP6_ADDRESS_SIZE);
+
+        if (Equal != FALSE) {
+            KeCancelTimer(Group->Timer.Timer);
+            Group->Flags &= ~MLD_MULTICAST_GROUP_FLAG_LAST_REPORT;
+            break;
+        }
+
+        CurrentEntry = CurrentEntry->Next;
+    }
+
+    KeReleaseQueuedLock(MldLink->Lock);
+    return;
+}
+
+VOID
+NetpMldQueueReportTimer (
+    PMLD_TIMER ReportTimer,
+    ULONGLONG StartTime,
+    ULONG MaxResponseTime
+    )
+
+/*++
+
+Routine Description:
+
+    This routine queues the given report timer to expire between 0 and the
+    maximum delay time from the given start time.
+
+Arguments:
+
+    ReportTimer - Supplies a pointer to the report timer that needs to be
+        queued.
+
+    StartTime - Supplies the starting time to which the calculated delay will
+        be added. This should be in timer ticks.
+
+    MaxResponseTime - Supplies the maximum responce time supplied by the MLD
+        query that prompted the report. It is in 1/10th of a second units.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONGLONG CurrentDueTime;
+    ULONG Delay;
+    ULONGLONG DelayInMicroseconds;
+    ULONGLONG DueTime;
+    KSTATUS Status;
+
+    //
+    // The random delay is selected from the range (0, MaxResponseTime].
+    //
+
+    KeGetRandomBytes(&Delay, sizeof(Delay));
+    Delay = (Delay % MaxResponseTime) + 1;
+    DelayInMicroseconds = Delay * MLD_MICROSECONDS_PER_QUERY_TIME_UNIT;
+    DueTime = StartTime + KeConvertMicrosecondsToTimeTicks(DelayInMicroseconds);
+    CurrentDueTime = KeGetTimerDueTime(ReportTimer->Timer);
+
+    //
+    // If the current due time is non-zero and less than the due time, do
+    // nothing. The report is already scheduled to be sent.
+    //
+
+    if ((CurrentDueTime != 0) && (CurrentDueTime <= DueTime)) {
+        return;
+    }
+
+    //
+    // Otherwise, cancel the timer and reschedule it for the earlier time. If
+    // the cancel is too late, then the timer just went off and the report
+    // will be sent. Do not reschedule the timer.
+    //
+
+    if (CurrentDueTime != 0) {
+        Status = KeCancelTimer(ReportTimer->Timer);
+        if (Status == STATUS_TOO_LATE) {
+            return;
+        }
+    }
+
+    KeQueueTimer(ReportTimer->Timer,
+                 TimerQueueSoft,
+                 DueTime,
+                 0,
+                 0,
+                 ReportTimer->Dpc);
+
+    return;
+}
+
+VOID
+NetpMldTimerDpcRoutine (
+    PDPC Dpc
+    )
+
+/*++
+
+Routine Description:
+
+    This routine implements the MLD timer DPC that gets called after a timer
+    expires.
+
+Arguments:
+
+    Dpc - Supplies a pointer to the DPC that is running.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD_TIMER ReportTimer;
+
+    ReportTimer = (PMLD_TIMER)Dpc->UserData;
+    KeQueueWorkItem(ReportTimer->WorkItem);
+    return;
+}
+
+VOID
+NetpMldGroupTimeoutWorker (
+    PVOID Parameter
+    )
+
+/*++
+
+Routine Description:
+
+    This routine performs the low level work when an MLD group report timer
+    expires. It sends a report or leave message for the group.
+
+Arguments:
+
+    Parameter - Supplies a pointer to the MLD group whose timer expired.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD_MULTICAST_GROUP Group;
+
+    //
+    // The worker thread should only send leave messages after the first leave
+    // message is sent by the initial leave request. The group will be
+    // destroyed after the last leave message, so don't touch the group
+    // structure after the call to send a leave message.
+    //
+
+    Group = (PMLD_MULTICAST_GROUP)Parameter;
+    if ((Group->Flags & MLD_MULTICAST_GROUP_FLAG_LEAVE_SENT) != 0) {
+        NetpMldSendGroupLeave(Group);
+
+    //
+    // Otherwise the timer has expired to send a simple group report.
+    //
+
+    } else {
+        NetpMldSendGroupReport(Group);
+    }
+
+    return;
+}
+
+VOID
+NetpMldLinkReportTimeoutWorker (
+    PVOID Parameter
+    )
+
+/*++
+
+Routine Description:
+
+    This routine performs the low level work when an MLD link report timer
+    expires. It sends a MLDv3 report message for all groups.
+
+Arguments:
+
+    Parameter - Supplies a pointer to the MLD link whose link report timer
+        expired.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD_LINK MldLink;
+
+    MldLink = (PMLD_LINK)Parameter;
+    NetpMldSendLinkReport(MldLink);
+    return;
+}
+
+VOID
+NetpMldLinkCompatibilityTimeoutWorker (
+    PVOID Parameter
+    )
+
+/*++
+
+Routine Description:
+
+    This routine performs the low level work when a compatibility mode timer
+    expires. It determines the new compatability mode.
+
+Arguments:
+
+    Parameter - Supplies a pointer to the MLD link whose compatibility timer
+        expired.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD_LINK MldLink;
+
+    MldLink = (PMLD_LINK)Parameter;
+    KeAcquireQueuedLock(MldLink->Lock);
+    NetpMldUpdateCompatibilityMode(MldLink);
+    KeReleaseQueuedLock(MldLink->Lock);
+    return;
+}
+
+VOID
+NetpMldQueueCompatibilityTimer (
+    PMLD_LINK MldLink,
+    MLD_VERSION CompatibilityMode
+    )
+
+/*++
+
+Routine Description:
+
+    This routine queues an MLD compatibility timer for the given mode.
+
+Arguments:
+
+    MldLink - Supplies a pointer to MLD link whose compatibility timer needs
+        to be set.
+
+    CompatibilityMode - Supplies a pointer to the compatibility mode whose
+        timer needs to be set.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONGLONG CurrentDueTime;
+    ULONG DelayInMicroseconds;
+    ULONGLONG DueTime;
+    ULONGLONG StartTime;
+    PMLD_TIMER Timer;
+
+    //
+    // The compatibility mode interval is calculated as follows:
+    //
+    // (Robustness Variable * Query Interval) + (Query Response Interval)
+    //
+    // The Query Response Interval is the same as the maximum response time
+    // provided by the last query.
+    //
+
+    DelayInMicroseconds = MldLink->RobustnessVariable *
+                          MldLink->QueryInterval *
+                          MICROSECONDS_PER_SECOND;
+
+    DelayInMicroseconds += MldLink->MaxResponseTime *
+                           MLD_MICROSECONDS_PER_QUERY_TIME_UNIT;
+
+    Timer = &(MldLink->CompatibilityTimer[CompatibilityMode]);
+    StartTime = KeGetRecentTimeCounter();
+    DueTime = StartTime + KeConvertMicrosecondsToTimeTicks(DelayInMicroseconds);
+
+    //
+    // If the timer is already scheduled, then it needs to be extended for
+    // another compatibility timeout interval. Cancel it and requeue it. It's
+    // OK if the DPC fires the work item in the meantime. The correct mode will
+    // be set once the lock can be acquired by the work item.
+    //
+
+    KeAcquireQueuedLock(MldLink->Lock);
+    CurrentDueTime = KeGetTimerDueTime(Timer->Timer);
+    if (CurrentDueTime != 0) {
+        KeCancelTimer(Timer->Timer);
+    }
+
+    KeQueueTimer(Timer->Timer,
+                 TimerQueueSoft,
+                 DueTime,
+                 0,
+                 0,
+                 Timer->Dpc);
+
+    NetpMldUpdateCompatibilityMode(MldLink);
+    KeReleaseQueuedLock(MldLink->Lock);
+    return;
+}
+
+VOID
+NetpMldUpdateCompatibilityMode (
+    PMLD_LINK MldLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine updates the given MLD link's compatibility mode based on the
+    state of the compatibility timers. It assumes the MLD link's lock is held.
+
+Arguments:
+
+    MldLink - Supplies a pointer to an MLD link.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PLIST_ENTRY CurrentEntry;
+    ULONGLONG DueTime;
+    PMLD_MULTICAST_GROUP Group;
+    MLD_VERSION ModeIndex;
+    MLD_VERSION NewMode;
+    PMLD_TIMER Timer;
+
+    ASSERT(KeIsQueuedLockHeld(MldLink->Lock) != FALSE);
+
+    NewMode = MldVersion2;
+    for (ModeIndex = MldVersion1;
+         ModeIndex < MLD_COMPATIBILITY_MODE_COUNT;
+         ModeIndex += 1) {
+
+        Timer = &(MldLink->CompatibilityTimer[ModeIndex]);
+        DueTime = KeGetTimerDueTime(Timer->Timer);
+        if (DueTime != 0) {
+            NewMode = ModeIndex;
+            break;
+        }
+    }
+
+    //
+    // If compatibility mode is about to change, cancel all pending timers.
+    //
+
+    if (NewMode != MldLink->CompatibilityMode) {
+        KeCancelTimer(MldLink->ReportTimer.Timer);
+        CurrentEntry = MldLink->MulticastGroupList.Next;
+        while (CurrentEntry != &(MldLink->MulticastGroupList)) {
+            Group = LIST_VALUE(CurrentEntry, MLD_MULTICAST_GROUP, ListEntry);
+            KeCancelTimer(Group->Timer.Timer);
+            CurrentEntry = CurrentEntry->Next;
+        }
+    }
+
+    MldLink->CompatibilityMode = NewMode;
+    return;
+}
+
+VOID
+NetpMldSendGroupReport (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends a MLD report message for a specific multicast group.
+
+Arguments:
+
+    Group - Supplies a pointer to the multicast group to report.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD2_ADDRESS_RECORD AddressRecord;
+    ULONG BufferFlags;
+    ULONG BufferSize;
+    MLD_VERSION CompatibilityMode;
+    IP6_ADDRESS Destination;
+    BOOL Equal;
+    PMLD_MESSAGE Message;
+    NET_PACKET_LIST NetPacketList;
+    PNET_PACKET_BUFFER Packet;
+    UCHAR RecordType;
+    PMLD2_REPORT Report;
+    KSTATUS Status;
+    UCHAR Type;
+
+    //
+    // Never send a report for the all-nodes multicast address.
+    //
+
+    Equal = RtlCompareMemory(Group->Address,
+                             NetIp6AllNodesMulticastAddress,
+                             IP6_ADDRESS_SIZE);
+
+    if (Equal != FALSE) {
+        return;
+    }
+
+    //
+    // Snap the compatibility mode.
+    //
+
+    CompatibilityMode = Group->MldLink->CompatibilityMode;
+    if (CompatibilityMode == MldVersion2) {
+        BufferSize = sizeof(MLD2_REPORT) + sizeof(MLD2_ADDRESS_RECORD);
+
+        ASSERT(BufferSize <= Group->MldLink->MaxPacketSize);
+
+    } else {
+        BufferSize = sizeof(MLD_MESSAGE);
+    }
+
+    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(MLD_IP6_HEADER_SIZE,
+                               BufferSize,
+                               0,
+                               Group->MldLink->Link,
+                               BufferFlags,
+                               &Packet);
+
+    if (!KSUCCESS(Status)) {
+        return;
+    }
+
+    Destination.Domain = NetDomainIp6;
+    Message = (PMLD_MESSAGE)(Packet->Buffer + Packet->DataOffset);
+    switch (CompatibilityMode) {
+    case MldVersion2:
+        Type = ICMP6_MESSAGE_TYPE_MLD2_REPORT;
+        RtlCopyMemory(Destination.Address,
+                      NetIp6AllMld2RoutersMulticastAddress,
+                      IP6_ADDRESS_SIZE);
+
+        Report = (PMLD2_REPORT)Message;
+        Report->Reserved = 0;
+        Report->AddressRecordCount = CPU_TO_NETWORK16(1);
+        AddressRecord = (PMLD2_ADDRESS_RECORD)(Report + 1);
+        if ((Group->Flags & MLD_MULTICAST_GROUP_FLAG_STATE_CHANGE) != 0) {
+            RecordType = MLD_ADDRESS_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE;
+
+        } else {
+            RecordType = MLD_ADDRESS_RECORD_TYPE_MODE_IS_EXCLUDE;
+        }
+
+        AddressRecord->Type = RecordType;
+        AddressRecord->DataLength = 0;
+        AddressRecord->SourceAddressCount = CPU_TO_NETWORK16(0);
+        RtlCopyMemory(AddressRecord->MulticastAddress,
+                      Group->Address,
+                      IP6_ADDRESS_SIZE);
+
+        break;
+
+    case MldVersion1:
+        Type = ICMP6_MESSAGE_TYPE_MLD_REPORT;
+        RtlCopyMemory(Message->MulticastAddress,
+                      Group->Address,
+                      IP6_ADDRESS_SIZE);
+
+        RtlCopyMemory(Destination.Address,
+                      Group->Address,
+                      IP6_ADDRESS_SIZE);
+
+        break;
+
+    default:
+
+        ASSERT(FALSE);
+
+        return;
+    }
+
+    NET_INITIALIZE_PACKET_LIST(&NetPacketList);
+    NET_ADD_PACKET_TO_LIST(Packet, &NetPacketList);
+    NetpMldSendPackets(Group->MldLink,
+                       (PNETWORK_ADDRESS)&Destination,
+                       &NetPacketList,
+                       Type);
+
+    //
+    // Note that this link sent the last report for this group, making it on
+    // the hook for sending the leave messages. Also test to see whether more
+    // join messages need to be sent.
+    //
+
+    KeAcquireQueuedLock(Group->MldLink->Lock);
+    Group->Flags |= MLD_MULTICAST_GROUP_FLAG_LAST_REPORT;
+    if (Group->ListEntry.Next != NULL) {
+        Group->SendCount -= 1;
+        if (Group->SendCount > 0) {
+            NetpMldQueueReportTimer(&(Group->Timer),
+                                    KeGetRecentTimeCounter(),
+                                    MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
+        }
+    }
+
+    KeReleaseQueuedLock(Group->MldLink->Lock);
+    return;
+}
+
+VOID
+NetpMldSendGroupLeave (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends an MLD leave message to the all routers multicast group.
+
+Arguments:
+
+    Group - Supplies a pointer to the multicast group that the host is leaving.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD2_ADDRESS_RECORD AddressRecord;
+    ULONG BufferFlags;
+    ULONG BufferSize;
+    MLD_VERSION CompatibilityMode;
+    IP6_ADDRESS Destination;
+    BOOL DestroyGroup;
+    BOOL Equal;
+    PMLD_MESSAGE Message;
+    NET_PACKET_LIST NetPacketList;
+    PNET_PACKET_BUFFER Packet;
+    PMLD2_REPORT Report;
+    KSTATUS Status;
+    UCHAR Type;
+
+    DestroyGroup = TRUE;
+
+    //
+    // Never send a leave report for the all-nodes multicast address.
+    //
+
+    Equal = RtlCompareMemory(Group->Address,
+                             NetIp6AllNodesMulticastAddress,
+                             IP6_ADDRESS_SIZE);
+
+    if (Equal != FALSE) {
+        return;
+    }
+
+    //
+    // If this link was not the last to report the group, then don't send
+    // a done message.
+    //
+
+    if ((Group->Flags & MLD_MULTICAST_GROUP_FLAG_LAST_REPORT) == 0) {
+        goto SendGroupLeaveEnd;
+    }
+
+    //
+    // Snap the current compatibility mode.
+    //
+
+    CompatibilityMode = Group->MldLink->CompatibilityMode;
+    if (CompatibilityMode == MldVersion1) {
+        BufferSize = sizeof(MLD_MESSAGE);
+
+    } else {
+        BufferSize = sizeof(MLD2_REPORT) + sizeof(MLD2_ADDRESS_RECORD);
+
+        ASSERT(CompatibilityMode == MldVersion2);
+        ASSERT(BufferSize <= Group->MldLink->MaxPacketSize);
+    }
+
+    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(MLD_IP6_HEADER_SIZE,
+                               BufferSize,
+                               0,
+                               Group->MldLink->Link,
+                               BufferFlags,
+                               &Packet);
+
+    if  (!KSUCCESS(Status)) {
+        goto SendGroupLeaveEnd;
+    }
+
+    Destination.Domain = NetDomainIp6;
+    Message = (PMLD_MESSAGE)(Packet->Buffer + Packet->DataOffset);
+    switch (CompatibilityMode) {
+    case MldVersion2:
+        Type = ICMP6_MESSAGE_TYPE_MLD2_REPORT;
+        RtlCopyMemory(Destination.Address,
+                      NetIp6AllMld2RoutersMulticastAddress,
+                      IP6_ADDRESS_SIZE);
+
+        Report = (PMLD2_REPORT)Message;
+        Report->Reserved = 0;
+        Report->AddressRecordCount = CPU_TO_NETWORK16(1);
+        AddressRecord = (PMLD2_ADDRESS_RECORD)(Report + 1);
+        AddressRecord->Type = MLD_ADDRESS_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE;
+        AddressRecord->DataLength = 0;
+        AddressRecord->SourceAddressCount = CPU_TO_NETWORK16(0);
+        RtlCopyMemory(AddressRecord->MulticastAddress,
+                      Group->Address,
+                      IP6_ADDRESS_SIZE);
+
+        break;
+
+    case MldVersion1:
+        Type = ICMP6_MESSAGE_TYPE_MLD_DONE;
+        Message = (PMLD_MESSAGE)Message;
+        RtlCopyMemory(Message->MulticastAddress,
+                      Group->Address,
+                      IP6_ADDRESS_SIZE);
+
+        RtlCopyMemory(Destination.Address,
+                      NetIp6AllRoutersMulticastAddress,
+                      IP6_ADDRESS_SIZE);
+
+        break;
+
+    default:
+
+        ASSERT(FALSE);
+
+        goto SendGroupLeaveEnd;
+    }
+
+    NET_INITIALIZE_PACKET_LIST(&NetPacketList);
+    NET_ADD_PACKET_TO_LIST(Packet, &NetPacketList);
+    NetpMldSendPackets(Group->MldLink,
+                       (PNETWORK_ADDRESS)&Destination,
+                       &NetPacketList,
+                       Type);
+
+    //
+    // Note that a leave message has now been sent, allowing the worker to send
+    // more leave messages. If the worker were to send leave messages before
+    // an initial leave message is sent by the leave request, it may be doing
+    // so on behalf of a previous join message. This messes up the send count
+    // and reference counting.
+    //
+
+    KeAcquireQueuedLock(Group->MldLink->Lock);
+    Group->Flags |= MLD_MULTICAST_GROUP_FLAG_LEAVE_SENT;
+
+    ASSERT(Group->SendCount > 0);
+
+    Group->SendCount -= 1;
+    if (Group->SendCount > 0) {
+        NetpMldQueueReportTimer(&(Group->Timer),
+                                KeGetRecentTimeCounter(),
+                                MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
+
+        DestroyGroup = FALSE;
+    }
+
+    KeReleaseQueuedLock(Group->MldLink->Lock);
+
+SendGroupLeaveEnd:
+    if (DestroyGroup != FALSE) {
+        NetpMldGroupReleaseReference(Group);
+    }
+
+    return;
+}
+
+VOID
+NetpMldSendLinkReport (
+    PMLD_LINK MldLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends a MLD report message for the whole link.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link to report.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    PMLD2_ADDRESS_RECORD AddressRecord;
+    ULONG BufferFlags;
+    ULONG BufferSize;
+    PLIST_ENTRY CurrentEntry;
+    ULONG CurrentRecordCount;
+    IP6_ADDRESS Destination;
+    PMLD_MULTICAST_GROUP Group;
+    NET_PACKET_LIST NetPacketList;
+    PNET_PACKET_BUFFER Packet;
+    ULONG RecordSize;
+    ULONG RemainingRecordCount;
+    PMLD2_REPORT Report;
+    USHORT SourceAddressCount;
+    KSTATUS Status;
+
+    //
+    // Send as many MLDv2 "Current-State" records as required to notify the
+    // all MLDv2-capable routers group of all the multicast groups to which the
+    // given link belongs. This may take more than one packet if the link is
+    // subscribed to more than MAX_USHORT groups or if the number of groups
+    // requires a packet larger than the link's max transfer size.
+    //
+
+    NET_INITIALIZE_PACKET_LIST(&NetPacketList);
+    KeAcquireQueuedLock(MldLink->Lock);
+    RemainingRecordCount = MldLink->ReportableGroupCount;
+    CurrentEntry = MldLink->MulticastGroupList.Next;
+    while (RemainingRecordCount != 0) {
+        CurrentRecordCount = RemainingRecordCount;
+        if (CurrentRecordCount > MLD_MAX_ADDRESS_RECORD_COUNT) {
+            CurrentRecordCount = MLD_MAX_ADDRESS_RECORD_COUNT;
+        }
+
+        BufferSize = sizeof(MLD2_REPORT) +
+                     (sizeof(MLD2_ADDRESS_RECORD) * CurrentRecordCount);
+
+        if (BufferSize > MldLink->MaxPacketSize) {
+            BufferSize = MldLink->MaxPacketSize;
+            CurrentRecordCount = (BufferSize - sizeof(MLD2_REPORT)) /
+                                sizeof(MLD2_ADDRESS_RECORD);
+        }
+
+        RemainingRecordCount -= CurrentRecordCount;
+        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(MLD_IP6_HEADER_SIZE,
+                                   BufferSize,
+                                   0,
+                                   MldLink->Link,
+                                   BufferFlags,
+                                   &Packet);
+
+        if (!KSUCCESS(Status)) {
+            break;
+        }
+
+        Report = (PMLD2_REPORT)Packet->Buffer + Packet->DataOffset;
+        Report->Reserved = 0;
+        Report->AddressRecordCount = CPU_TO_NETWORK16(CurrentRecordCount);
+        AddressRecord = (PMLD2_ADDRESS_RECORD)(Report + 1);
+        while (CurrentRecordCount != 0) {
+
+            ASSERT(CurrentEntry != &(MldLink->MulticastGroupList));
+
+            //
+            // Skip any groups that are not reportable. They were not included
+            // in the total reportable count.
+            //
+
+            Group = LIST_VALUE(CurrentEntry, MLD_MULTICAST_GROUP, ListEntry);
+            CurrentEntry = CurrentEntry->Next;
+            if (NetpMldIsReportableGroup(Group) == FALSE) {
+                continue;
+            }
+
+            CurrentRecordCount -= 1;
+
+            //
+            // The count should be accurate and eliminate the need to check for
+            // the head.
+            //
+
+            AddressRecord->Type = MLD_ADDRESS_RECORD_TYPE_MODE_IS_EXCLUDE;
+            AddressRecord->DataLength = 0;
+            SourceAddressCount = 0;
+            AddressRecord->SourceAddressCount =
+                                          CPU_TO_NETWORK16(SourceAddressCount);
+
+            RtlCopyMemory(AddressRecord->MulticastAddress,
+                          Group->Address,
+                          IP6_ADDRESS_SIZE);
+
+            RecordSize = sizeof(MLD2_ADDRESS_RECORD) +
+                        (SourceAddressCount * sizeof(ULONG)) +
+                        AddressRecord->DataLength;
+
+            AddressRecord =
+                    (PMLD2_ADDRESS_RECORD)((PUCHAR)AddressRecord + RecordSize);
+        }
+
+        NET_ADD_PACKET_TO_LIST(Packet, &NetPacketList);
+    }
+
+    KeReleaseQueuedLock(MldLink->Lock);
+    if (NET_PACKET_LIST_EMPTY(&NetPacketList) != FALSE) {
+        return;
+    }
+
+    Destination.Domain = NetDomainIp6;
+    RtlCopyMemory(Destination.Address,
+                  NetIp6AllMld2RoutersMulticastAddress,
+                  IP6_ADDRESS_SIZE);
+
+    NetpMldSendPackets(MldLink,
+                       (PNETWORK_ADDRESS)&Destination,
+                       &NetPacketList,
+                       ICMP6_MESSAGE_TYPE_MLD2_REPORT);
+
+    return;
+}
+
+VOID
+NetpMldSendPackets (
+    PMLD_LINK MldLink,
+    PNETWORK_ADDRESS Destination,
+    PNET_PACKET_LIST PacketList,
+    UCHAR Type
+    )
+
+/*++
+
+Routine Description:
+
+    This routine sends a list of MLD 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:
+
+    MldLink - Supplies a pointer to the MLD link over which to send the
+        packet.
+
+    Destination - Supplies a pointer to the destination address. This should be
+        a multicast address.
+
+    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;
+    NETWORK_ADDRESS DestinationPhysical;
+    PICMP6_HEADER Icmp6Header;
+    ULONG Icmp6Length;
+    PIP6_EXTENSION_HEADER Ip6ExtensionHeader;
+    PIP6_HEADER Ip6Header;
+    PIP6_OPTION Ip6Option;
+    PNET_LINK Link;
+    PNET_LINK_ADDRESS_ENTRY LinkAddress;
+    PNET_PACKET_BUFFER Packet;
+    ULONG PayloadLength;
+    PUSHORT RouterAlertCode;
+    PNET_DATA_LINK_SEND Send;
+    PIP6_ADDRESS Source;
+    KSTATUS Status;
+    IP6_ADDRESS UnspecifiedAddress;
+    ULONG VersionClassFlow;
+
+    //
+    // For each packet in the list, add an ICMPv6 and IPv6 header.
+    //
+
+    Link = MldLink->Link;
+    LinkAddress = MldLink->LinkAddress;
+
+    //
+    // The source address must be link local or the unspecified address.
+    //
+
+    if (LinkAddress->Configured != FALSE) {
+        Source = (PIP6_ADDRESS)&(LinkAddress->Address);
+
+        ASSERT(IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(Source->Address) != FALSE);
+
+    } else {
+        RtlZeroMemory(&UnspecifiedAddress, sizeof(IP6_ADDRESS));
+        UnspecifiedAddress.Domain = NetDomainIp6;
+        Source = &UnspecifiedAddress;
+    }
+
+    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 MLD 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;
+
+        //
+        // Add the IPv6 extended header. Work backwards from the Pad-N option.
+        //
+
+        Packet->DataOffset -= sizeof(IP6_OPTION);
+        Ip6Option = (PIP6_OPTION)(Packet->Buffer + Packet->DataOffset);
+        Ip6Option->Type = IP6_OPTION_TYPE_PADN;
+        Ip6Option->Length = 0;
+        Packet->DataOffset -= sizeof(USHORT);
+        RouterAlertCode = (PUSHORT)(Packet->Buffer + Packet->DataOffset);
+        *RouterAlertCode = CPU_TO_NETWORK16(IP6_ROUTER_ALERT_CODE_MLD);
+        Packet->DataOffset -= sizeof(IP6_OPTION);
+        Ip6Option = (PIP6_OPTION)(Packet->Buffer + Packet->DataOffset);
+        Ip6Option->Type = IP6_OPTION_TYPE_ROUTER_ALERT;
+        Ip6Option->Length = sizeof(USHORT);
+        Packet->DataOffset -= sizeof(IP6_EXTENSION_HEADER);
+
+        //
+        // The extension header length is measured in 8 byte units and does not
+        // include the first 8 bytes. Thus, it is zero in this instance.
+        //
+
+        Ip6ExtensionHeader = (PIP6_EXTENSION_HEADER)(Packet->Buffer +
+                                                     Packet->DataOffset);
+
+        Ip6ExtensionHeader->NextHeader = SOCKET_INTERNET_PROTOCOL_ICMP6;
+        Ip6ExtensionHeader->Length = 0;
+
+        //
+        // Now add the IPv6 header.
+        //
+
+        PayloadLength = Packet->FooterOffset - Packet->DataOffset;
+        if (PayloadLength > IP6_MAX_PAYLOAD_LENGTH) {
+            Status = STATUS_MESSAGE_TOO_LONG;
+            goto MldSendPacketsEnd;
+        }
+
+        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_HOPOPT;
+        Ip6Header->HopLimit = MLD_IP6_HOP_LIMIT;
+        RtlCopyMemory(Ip6Header->SourceAddress,
+                      Source->Address,
+                      IP6_ADDRESS_SIZE);
+
+        RtlCopyMemory(Ip6Header->DestinationAddress,
+                      Destination->Address,
+                      IP6_ADDRESS_SIZE);
+    }
+
+    //
+    // Get the physical address for the IPv6 address.
+    //
+
+    ASSERT(IP6_IS_MULTICAST_ADDRESS(((PIP6_ADDRESS)Destination)->Address));
+
+    Status = Link->DataLinkEntry->Interface.ConvertToPhysicalAddress(
+                                                          Destination,
+                                                          &DestinationPhysical,
+                                                          NetAddressMulticast);
+
+    if (!KSUCCESS(Status)) {
+        goto MldSendPacketsEnd;
+    }
+
+    Send = Link->DataLinkEntry->Interface.Send;
+    Status = Send(Link->DataLinkContext,
+                  PacketList,
+                  &(LinkAddress->PhysicalAddress),
+                  &DestinationPhysical,
+                  IP6_PROTOCOL_NUMBER);
+
+    if (!KSUCCESS(Status)) {
+        goto MldSendPacketsEnd;
+    }
+
+MldSendPacketsEnd:
+    return;
+}
+
+PMLD_LINK
+NetpMldCreateOrLookupLink (
+    PNET_LINK Link,
+    PNET_LINK_ADDRESS_ENTRY LinkAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creates an MLD link associated with the given local address
+    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 MLD link is
+        to be created.
+
+    LinkAddress - Supplies a pointer to the link address entry on the given
+        network link with which the MLD link shall be associated.
+
+Return Value:
+
+    Returns a pointer to the newly allocated MLD link on success or NULL on
+    failure.
+
+--*/
+
+{
+
+    PNET_DATA_LINK_ENTRY DataLinkEntry;
+    NET_PACKET_SIZE_INFORMATION DataSizeInformation;
+    PRED_BLACK_TREE_NODE FoundNode;
+    ULONG Index;
+    PNET_PACKET_SIZE_INFORMATION LinkSizeInformation;
+    ULONG MaxPacketSize;
+    PMLD_LINK MldLink;
+    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;
+        goto CreateOrLookupLinkEnd;
+    }
+
+    RtlZeroMemory(NewMldLink, sizeof(MLD_LINK));
+    NewMldLink->ReferenceCount = 1;
+    NetLinkAddReference(Link);
+    NewMldLink->Link = Link;
+    NewMldLink->LinkAddress = LinkAddress;
+    NewMldLink->RobustnessVariable = MLD_DEFAULT_ROBUSTNESS_VARIABLE;
+    NewMldLink->QueryInterval = MLD_DEFAULT_QUERY_INTERVAL;
+    NewMldLink->MaxResponseTime = MLD_DEFAULT_MAX_RESPONSE_TIME;
+    NewMldLink->CompatibilityMode = MldVersion2;
+    INITIALIZE_LIST_HEAD(&(NewMldLink->MulticastGroupList));
+    NewMldLink->Lock = KeCreateQueuedLock();
+    if (NewMldLink->Lock == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto CreateOrLookupLinkEnd;
+    }
+
+    //
+    // Determine the maximum allowed MLD packet size based on the link.
+    //
+
+    LinkSizeInformation = &(Link->Properties.PacketSizeInformation);
+    MaxPacketSize = LinkSizeInformation->MaxPacketSize;
+    DataLinkEntry = Link->DataLinkEntry;
+    DataLinkEntry->Interface.GetPacketSizeInformation(Link->DataLinkContext,
+                                                      &DataSizeInformation,
+                                                      0);
+
+    if (MaxPacketSize > DataSizeInformation.MaxPacketSize) {
+        MaxPacketSize = DataSizeInformation.MaxPacketSize;
+    }
+
+    MaxPacketSize -= (LinkSizeInformation->HeaderSize +
+                      LinkSizeInformation->FooterSize +
+                      DataSizeInformation.HeaderSize +
+                      DataSizeInformation.FooterSize +
+                      MLD_IP6_HEADER_SIZE);
+
+    NewMldLink->MaxPacketSize = MaxPacketSize;
+    Status = NetpMldInitializeTimer(&(NewMldLink->ReportTimer),
+                                     NetpMldLinkReportTimeoutWorker,
+                                     NewMldLink);
+
+    if (!KSUCCESS(Status)) {
+        goto CreateOrLookupLinkEnd;
+    }
+
+    //
+    // Initialize the compatibility mode counters.
+    //
+
+    for (Index = 0; Index < MLD_COMPATIBILITY_MODE_COUNT; Index += 1) {
+        Status = NetpMldInitializeTimer(
+                                     &(NewMldLink->CompatibilityTimer[Index]),
+                                     NetpMldLinkCompatibilityTimeoutWorker,
+                                     NewMldLink);
+
+        if (!KSUCCESS(Status)) {
+            goto CreateOrLookupLinkEnd;
+        }
+    }
+
+    //
+    // Attempt to insert the new MLD link into the tree. If an existing link
+    // is found, use that one and destroy the new one.
+    //
+
+    SearchLink.Link = Link;
+    KeAcquireSharedExclusiveLockExclusive(NetMldLinkLock);
+    TreeLockHeld = TRUE;
+    FoundNode = RtlRedBlackTreeSearch(&NetMldLinkTree, &(SearchLink.Node));
+    if (FoundNode == NULL) {
+        RtlRedBlackTreeInsert(&NetMldLinkTree, &(NewMldLink->Node));
+        MldLink = NewMldLink;
+        NewMldLink = NULL;
+
+    } else {
+        MldLink = RED_BLACK_TREE_VALUE(FoundNode, MLD_LINK, Node);
+    }
+
+    NetpMldLinkAddReference(MldLink);
+    KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
+    TreeLockHeld = FALSE;
+
+CreateOrLookupLinkEnd:
+    if (TreeLockHeld != FALSE) {
+        KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
+    }
+
+    if (NewMldLink != NULL) {
+        NetpMldLinkReleaseReference(NewMldLink);
+    }
+
+    return MldLink;
+}
+
+VOID
+NetpMldDestroyLink (
+    PMLD_LINK MldLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys an MLD link and all of its resources.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG Index;
+
+    ASSERT(MldLink->ReferenceCount == 0);
+    ASSERT(LIST_EMPTY(&(MldLink->MulticastGroupList)) != FALSE);
+
+    NetpMldDestroyTimer(&(MldLink->ReportTimer));
+    for (Index = 0; Index < MLD_COMPATIBILITY_MODE_COUNT; Index += 1) {
+        NetpMldDestroyTimer(&(MldLink->CompatibilityTimer[Index]));
+    }
+
+    if (MldLink->Lock != NULL) {
+        KeDestroyQueuedLock(MldLink->Lock);
+    }
+
+    NetLinkReleaseReference(MldLink->Link);
+    MmFreePagedPool(MldLink);
+    return;
+}
+
+PMLD_LINK
+NetpMldLookupLink (
+    PNET_LINK Link
+    )
+
+/*++
+
+Routine Description:
+
+    This routine finds an MLD link associated with the given network link. The
+    caller is expected to release a reference on the MLD link.
+
+Arguments:
+
+    Link - Supplies a pointer to the network link, which is used to look up the
+        MLD link.
+
+Return Value:
+
+    Returns a pointer to the matching MLD link on success or NULL on failure.
+
+--*/
+
+{
+
+    PRED_BLACK_TREE_NODE FoundNode;
+    PMLD_LINK MldLink;
+    MLD_LINK SearchLink;
+
+    MldLink = NULL;
+    SearchLink.Link = Link;
+    KeAcquireSharedExclusiveLockShared(NetMldLinkLock);
+    FoundNode = RtlRedBlackTreeSearch(&NetMldLinkTree, &(SearchLink.Node));
+    if (FoundNode == NULL) {
+        goto FindLinkEnd;
+    }
+
+    MldLink = RED_BLACK_TREE_VALUE(FoundNode, MLD_LINK, Node);
+    NetpMldLinkAddReference(MldLink);
+
+FindLinkEnd:
+    KeReleaseSharedExclusiveLockShared(NetMldLinkLock);
+    return MldLink;
+}
+
+VOID
+NetpMldLinkAddReference (
+    PMLD_LINK MldLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine increments the reference count of an MLD link.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG OldReferenceCount;
+
+    OldReferenceCount = RtlAtomicAdd32(&(MldLink->ReferenceCount), 1);
+
+    ASSERT(OldReferenceCount < 0x10000000);
+
+    return;
+}
+
+VOID
+NetpMldLinkReleaseReference (
+    PMLD_LINK MldLink
+    )
+
+/*++
+
+Routine Description:
+
+    This routine releases a reference on an MLD link.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    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.
+    //
+
+    KeAcquireSharedExclusiveLockExclusive(NetMldLinkLock);
+    OldReferenceCount = RtlAtomicAdd32(&(MldLink->ReferenceCount), (ULONG)-1);
+
+    ASSERT((OldReferenceCount != 0) && (OldReferenceCount < 0x10000000));
+
+    //
+    // If the second reference was just released, then the last references is
+    // from creation. No multicast groups have a reference on the link and as
+    // the tree lock is held exclusively, no other threads have references on
+    // the link. Therefore, the link can be removed from the tree.
+    //
+
+    if (OldReferenceCount == 2) {
+
+        ASSERT(LIST_EMPTY(&(MldLink->MulticastGroupList)) != FALSE);
+        ASSERT(MldLink->ReportableGroupCount == 0);
+
+        RtlRedBlackTreeRemove(&NetMldLinkTree, &(MldLink->Node));
+        MldLink->Node.Parent = NULL;
+        KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
+        NetpMldLinkReleaseReference(MldLink);
+
+    } else {
+        KeReleaseSharedExclusiveLockExclusive(NetMldLinkLock);
+        if (OldReferenceCount == 1) {
+            NetpMldDestroyLink(MldLink);
+        }
+    }
+
+    return;
+}
+
+COMPARISON_RESULT
+NetpMldCompareLinkEntries (
+    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.
+
+--*/
+
+{
+
+    PMLD_LINK FirstMldLink;
+    PMLD_LINK SecondMldLink;
+
+    FirstMldLink = RED_BLACK_TREE_VALUE(FirstNode, MLD_LINK, Node);
+    SecondMldLink = RED_BLACK_TREE_VALUE(FirstNode, MLD_LINK, Node);
+    if (FirstMldLink->Link == SecondMldLink->Link) {
+        return ComparisonResultSame;
+
+    } else if (FirstMldLink->Link < SecondMldLink->Link) {
+        return ComparisonResultAscending;
+    }
+
+    return ComparisonResultDescending;
+}
+
+PMLD_MULTICAST_GROUP
+NetpMldCreateGroup (
+    PMLD_LINK MldLink,
+    PIP6_ADDRESS GroupAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine creats an MLD multicast group structure.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link to which the multicast group
+        will belong.
+
+    GroupAddress - Supplies a pointer to the IPv6 multicast address for the
+        group.
+
+Return Value:
+
+    Returns a pointer to the newly allocated multicast group.
+
+--*/
+
+{
+
+    PMLD_MULTICAST_GROUP Group;
+    KSTATUS Status;
+
+    Group = MmAllocatePagedPool(sizeof(MLD_MULTICAST_GROUP),
+                                MLD_ALLOCATION_TAG);
+
+    if (Group == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto CreateGroupEnd;
+    }
+
+    RtlZeroMemory(Group, sizeof(MLD_MULTICAST_GROUP));
+    Group->ReferenceCount = 1;
+    Group->JoinCount = 1;
+    NetpMldLinkAddReference(MldLink);
+    Group->MldLink = MldLink;
+    RtlCopyMemory(Group->Address, GroupAddress->Address, IP6_ADDRESS_SIZE);
+    Status = NetpMldInitializeTimer(&(Group->Timer),
+                                     NetpMldGroupTimeoutWorker,
+                                     Group);
+
+    if (!KSUCCESS(Status)) {
+        goto CreateGroupEnd;
+    }
+
+CreateGroupEnd:
+    if (!KSUCCESS(Status)) {
+        if (Group != NULL) {
+            NetpMldDestroyGroup(Group);
+            Group = NULL;
+        }
+    }
+
+    return Group;
+}
+
+VOID
+NetpMldDestroyGroup (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys all the resources for the given multicast group.
+
+Arguments:
+
+    Group - Supplies a pointer to the group to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ASSERT(Group->JoinCount == 0);
+
+    NetpMldDestroyTimer(&(Group->Timer));
+    NetpMldLinkReleaseReference(Group->MldLink);
+    MmFreePagedPool(Group);
+    return;
+}
+
+PMLD_MULTICAST_GROUP
+NetpMldLookupGroup (
+    PMLD_LINK MldLink,
+    PIP6_ADDRESS GroupAddress
+    )
+
+/*++
+
+Routine Description:
+
+    This routine finds a multicast group with the given address that the given
+    link has joined. It takes a reference on the found group.
+
+Arguments:
+
+    MldLink - Supplies a pointer to the MLD link that owns the group to find.
+
+    GroupAddress - Supplies a pointer to the IPv6 multicast address of the
+        group.
+
+Return Value:
+
+    Returns a pointer to a multicast group on success or NULL on failure.
+
+--*/
+
+{
+
+    PLIST_ENTRY CurrentEntry;
+    BOOL Equal;
+    PMLD_MULTICAST_GROUP Group;
+
+    ASSERT(KeIsQueuedLockHeld(MldLink->Lock) != FALSE);
+
+    CurrentEntry = MldLink->MulticastGroupList.Next;
+    while (CurrentEntry != &(MldLink->MulticastGroupList)) {
+        Group = LIST_VALUE(CurrentEntry, MLD_MULTICAST_GROUP, ListEntry);
+        Equal = RtlCompareMemory(Group->Address,
+                                 GroupAddress->Address,
+                                 IP6_ADDRESS_SIZE);
+
+        if (Equal != FALSE) {
+            NetpMldGroupAddReference(Group);
+            return Group;
+        }
+
+        CurrentEntry = CurrentEntry->Next;
+    }
+
+    return NULL;
+}
+
+VOID
+NetpMldGroupAddReference (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine increments the reference count of an MLD multicast group.
+
+Arguments:
+
+    Group - Supplies a pointer to the MLD multicast group.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG OldReferenceCount;
+
+    OldReferenceCount = RtlAtomicAdd32(&(Group->ReferenceCount), 1);
+
+    ASSERT(OldReferenceCount < 0x10000000);
+
+    return;
+}
+
+VOID
+NetpMldGroupReleaseReference (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine releases a reference on an MLD multicast group.
+
+Arguments:
+
+    Group - Supplies a pointer to the MLD multicast group.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    ULONG OldReferenceCount;
+
+    OldReferenceCount = RtlAtomicAdd32(&(Group->ReferenceCount), (ULONG)-1);
+
+    ASSERT((OldReferenceCount != 0) && (OldReferenceCount < 0x10000000));
+
+    if (OldReferenceCount == 1) {
+        NetpMldDestroyGroup(Group);
+    }
+
+    return;
+}
+
+KSTATUS
+NetpMldInitializeTimer (
+    PMLD_TIMER Timer,
+    PWORK_ITEM_ROUTINE WorkRoutine,
+    PVOID WorkParameter
+    )
+
+/*++
+
+Routine Description:
+
+    This routine initializes the given MLD timer, setting up its timer, DPC,
+    and work item.
+
+Arguments:
+
+    Timer - Supplies a pointer to the MLD timer to initialize.
+
+    WorkRoutine - Supplies a pointer to the routine that runs when the work
+        item is scheduled.
+
+    WorkParameter - Supplies a pointer that is passed to the work routine when
+        it is invoked.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+{
+
+    KSTATUS Status;
+
+    Timer->Timer = KeCreateTimer(MLD_ALLOCATION_TAG);
+    if (Timer->Timer == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto InitializeTimerEnd;
+    }
+
+    Timer->Dpc = KeCreateDpc(NetpMldTimerDpcRoutine, Timer);
+    if (Timer->Dpc == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto InitializeTimerEnd;
+    }
+
+    Timer->WorkItem = KeCreateWorkItem(NULL,
+                                       WorkPriorityNormal,
+                                       WorkRoutine,
+                                       WorkParameter,
+                                       MLD_ALLOCATION_TAG);
+
+    if (Timer->WorkItem == NULL) {
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto InitializeTimerEnd;
+    }
+
+    Status = STATUS_SUCCESS;
+
+InitializeTimerEnd:
+    if (!KSUCCESS(Status)) {
+        NetpMldDestroyTimer(Timer);
+    }
+
+    return Status;
+}
+
+VOID
+NetpMldDestroyTimer (
+    PMLD_TIMER Timer
+    )
+
+/*++
+
+Routine Description:
+
+    This routine destroys all the resources of an MLD timer. It does not
+    release the structure itself, as it is ususally embedded within another
+    structure.
+
+Arguments:
+
+    Timer - Supplies a pointer to an MLD timer to destroy.
+
+Return Value:
+
+    None.
+
+--*/
+
+{
+
+    if (Timer->Timer != NULL) {
+        KeDestroyTimer(Timer->Timer);
+    }
+
+    if (Timer->Dpc != NULL) {
+        KeDestroyDpc(Timer->Dpc);
+    }
+
+    if (Timer->WorkItem != NULL) {
+        KeDestroyWorkItem(Timer->WorkItem);
+    }
+
+    return;
+}
+
+BOOL
+NetpMldIsReportableGroup (
+    PMLD_MULTICAST_GROUP Group
+    )
+
+/*++
+
+Routine Description:
+
+    This routine determines whether or not the given group should be reported
+    in MLD link-wide reports.
+
+Arguments:
+
+    Group - Supplies a pointer to an MLD multicast group.
+
+Return Value:
+
+    Returns TRUE if the group should be reported or FALSE otherwise.
+
+--*/
+
+{
+
+    BOOL Equal;
+
+    Equal = RtlCompareMemory(Group->Address,
+                             NetIp6AllNodesMulticastAddress,
+                             IP6_ADDRESS_SIZE);
+
+    if (Equal != FALSE) {
+        return FALSE;
+    }
+
+    return TRUE;
+}
+

+ 167 - 0
drivers/net/netcore/ipv6/mld.h

@@ -0,0 +1,167 @@
+/*++
+
+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:
+
+    mld.h
+
+Abstract:
+
+    This header contains definitions for Multicast Listener Discovery for IPv6.
+    It is a sub-protocol of ICMPv6.
+
+Author:
+
+    Chris Stevens 25-Aug-2017
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// --------------------------------------------------------------------- Macros
+//
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+/*++
+
+Structure Description:
+
+    This structure defines an MLD request to join or leave a multicast group.
+
+Members:
+
+    Link - Supplies a pointer to the network link associated with the multicast
+        group. Requests will send MLD notifications over this link and update
+        the address filters in this link's physical layer.
+
+    LinkAddress - Supplies a pointer to the link address entry with which the
+        multicast group is associated.
+
+    MulticastAddress - Supplies the IPv6 multicast group address.
+
+--*/
+
+typedef struct _SOCKET_MLD_MULTICAST_REQUEST {
+    PNET_LINK Link;
+    PNET_LINK_ADDRESS_ENTRY LinkAddress;
+    UCHAR MulticastAddress[IP6_ADDRESS_SIZE];
+} SOCKET_MLD_MULTICAST_REQUEST, *PSOCKET_MLD_MULTICAST_REQUEST;
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+//
+// -------------------------------------------------------- Function Prototypes
+//
+
+VOID
+NetpMldInitialize (
+    VOID
+    );
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for the MLD protocol.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
+VOID
+NetpMldProcessReceivedData (
+    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
+NetpMldJoinMulticastGroup (
+    PNET_NETWORK_MULTICAST_REQUEST Request
+    );
+
+/*++
+
+Routine Description:
+
+    This routine joins the multicast group on the network link provided in the
+    request. If this is the first request to join the supplied multicast group
+    on the specified link, then an MLD report is sent out over the network.
+
+Arguments:
+
+    Request - Supplies a pointer to a multicast request, which includes the
+        address of the group to join and the network link on which to join the
+        group.
+
+Return Value:
+
+    Status code.
+
+--*/
+
+KSTATUS
+NetpMldLeaveMulticastGroup (
+    PNET_NETWORK_MULTICAST_REQUEST Request
+    );
+
+/*++
+
+Routine Description:
+
+    This routine removes the local system from a multicast group. If this is
+    the last request to leave a multicast group on the link, then a IGMP leave
+    message is sent out over the network.
+
+Arguments:
+
+    Request - Supplies a pointer to a multicast request, which includes the
+        address of the group to leave and the network link that has previously
+        joined the group.
+
+Return Value:
+
+    Status code.
+
+--*/
+

+ 21 - 0
drivers/net/netcore/netcore.h

@@ -356,6 +356,27 @@ Return Value:
 
 --*/
 
+VOID
+NetpIcmp6Initialize (
+    VOID
+    );
+
+/*++
+
+Routine Description:
+
+    This routine initializes support for the ICMPv6 protocol.
+
+Arguments:
+
+    None.
+
+Return Value:
+
+    None.
+
+--*/
+
 VOID
 NetpNetlinkGenericInitialize (
     ULONG Phase

+ 3 - 0
include/minoca/kernel/knet.h

@@ -212,12 +212,14 @@ Author:
 // Define common internet protocol numbers, as defined by the IANA.
 //
 
+#define SOCKET_INTERNET_PROTOCOL_HOPOPT 0
 #define SOCKET_INTERNET_PROTOCOL_ICMP 1
 #define SOCKET_INTERNET_PROTOCOL_IGMP 2
 #define SOCKET_INTERNET_PROTOCOL_IPV4 4
 #define SOCKET_INTERNET_PROTOCOL_TCP 6
 #define SOCKET_INTERNET_PROTOCOL_UDP 17
 #define SOCKET_INTERNET_PROTOCOL_IPV6 41
+#define SOCKET_INTERNET_PROTOCOL_ICMP6 58
 
 //
 // Define non-IANA protocol numbers starting with the raw protocol at 255, the
@@ -339,6 +341,7 @@ typedef enum _SOCKET_INFORMATION_TYPE {
     SocketInformationIp6 = SOCKET_INTERNET_PROTOCOL_IPV6,
     SocketInformationTcp = SOCKET_INTERNET_PROTOCOL_TCP,
     SocketInformationUdp = SOCKET_INTERNET_PROTOCOL_UDP,
+    SocketInformationIcmp6 = SOCKET_INTERNET_PROTOCOL_ICMP6,
     SocketInformationRaw = SOCKET_INTERNET_PROTOCOL_RAW,
     SocketInformationNetlink = SOCKET_INTERNET_PROTOCOL_NETLINK,
     SocketInformationNetlinkGeneric = SOCKET_INTERNET_PROTOCOL_NETLINK_GENERIC

+ 105 - 0
include/minoca/net/icmp6.h

@@ -0,0 +1,105 @@
+/*++
+
+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:
+
+    icmp6.h
+
+Abstract:
+
+    This header contains public definitions for the IGMP protocol layer.
+
+Author:
+
+    Chris Stevens 25-Aug-2017
+
+--*/
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+//
+// --------------------------------------------------------------------- Macros
+//
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// Define ICMPv6 message types.
+//
+
+#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_MLD2_REPORT 143
+
+//
+// ------------------------------------------------------ Data Type Definitions
+//
+
+/*++
+
+Structure Description:
+
+    This structure defines the header for ICMPv6 messages.
+
+Members:
+
+    Type - Stores the ICMPv6 message type.
+
+    Code - Stores the ICMPv6 message code.
+
+    Checksum - Stores the ICMP checksum of the ICMPv6 message.
+
+--*/
+
+typedef struct _ICMP6_HEADER {
+    BYTE Type;
+    BYTE Code;
+    USHORT Checksum;
+} ICMP6_HEADER, *PICMP6_HEADER;
+
+/*++
+
+Enumeration Description:
+
+    This enumeration describes the various ICMPv6 options for the ICMPv6 socket
+    information class.
+
+Values:
+
+    SocketIcmp6OptionInvalid - Indicates an invalid ICMPv6 socket option.
+
+    SocketIcmp6OptionJoinMulticastGroup - Indicates a request to join a
+        multicast group. This option takes a NET_NETWORK_MULTICAST_REQUEST
+        structure.
+
+    SocketIcmp6OptionLeaveMulticastGroup - Indicates a request to leave a
+        multicast group. This option takes a NET_NETWORK_MULTICAST_REQUEST
+        structure.
+
+--*/
+
+typedef enum _SOCKET_ICMP6_OPTION {
+    SocketIcmp6OptionInvalid,
+    SocketIcmp6OptionJoinMulticastGroup,
+    SocketIcmp6OptionLeaveMulticastGroup
+} SOCKET_ICMP6_OPTION, *PSOCKET_ICMP6_OPTION;
+
+//
+// -------------------------------------------------------------------- Globals
+//
+
+//
+// -------------------------------------------------------- Function Prototypes
+//
+

+ 68 - 11
include/minoca/net/ip6.h

@@ -29,11 +29,11 @@ Author:
 // address.
 //
 
-#define IP6_IS_ANY_ADDRESS(_Ip6Address)                \
-    ((*((PULONG)&((_Ip6Address)->Address[0])) == 0) && \
-     (*((PULONG)&((_Ip6Address)->Address[4])) == 0) && \
-     (*((PULONG)&((_Ip6Address)->Address[8])) == 0) && \
-     (*((PULONG)&((_Ip6Address)->Address[12])) == 0))
+#define IP6_IS_ANY_ADDRESS(_Ip6Address)     \
+    ((*((PULONG)&(_Ip6Address[0])) == 0) && \
+     (*((PULONG)&(_Ip6Address[4])) == 0) && \
+     (*((PULONG)&(_Ip6Address[8])) == 0) && \
+     (*((PULONG)&(_Ip6Address[12])) == 0))
 
 //
 // This macros determines whether or not the given IPv6 address is a multicast
@@ -41,7 +41,7 @@ Author:
 //
 
 #define IP6_IS_MULTICAST_ADDRESS(_Ip6Address) \
-    ((_Ip6Address)->Address[0] == 0xFF)
+    ((_Ip6Address[0]) == 0xFF)
 
 //
 // This macros determines whether or not the given IPv6 address is a multicast
@@ -50,17 +50,16 @@ Author:
 
 #define IP6_IS_MULTICAST_LINK_LOCAL_ADDRESS(_Ip6Address) \
     (IN6_IS_ADDR_MULTICAST(_Ip6Address) &&               \
-     (((_Ip6Address)->Address[1] & 0x0F) == 0x2))
+     (((_Ip6Address[1]) & 0x0F) == 0x2))
 
 //
 // This macros determines whether or not the given IPv6 address is a unicast
 // link-local address.
 //
 
-#define IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(_Ip6Address) \
-    ((*((PULONG)&((_Ip6Address)->Address[0])) ==       \
-      CPU_TO_NETWORK32(0xFE800000)) &&                 \
-     (*((PULONG)&((_Ip6Address)->Address[4])) == 0))
+#define IP6_IS_UNICAST_LINK_LOCAL_ADDRESS(_Ip6Address)                 \
+    ((*((PULONG)&(_Ip6Address[0])) == CPU_TO_NETWORK32(0xFE800000)) && \
+     (*((PULONG)&(_Ip6Address[4])) == 0))
 
 //
 // ---------------------------------------------------------------- Definitions
@@ -103,6 +102,22 @@ Author:
 
 #define IP6_ADDRESS_SIZE 16
 
+//
+// Define the IPv6 extension header options types.
+//
+
+#define IP6_OPTION_TYPE_PAD1 0
+#define IP6_OPTION_TYPE_PADN 1
+#define IP6_OPTION_TYPE_ROUTER_ALERT 5
+
+//
+// Define the IPv6 router alert codes.
+//
+
+#define IP6_ROUTER_ALERT_CODE_MLD 0
+#define IP6_ROUTER_ALERT_CODE_RSVP 1
+#define IP6_ROUTER_ALERT_CODE_ACTIVE_NETWORK 2
+
 //
 // ------------------------------------------------------ Data Type Definitions
 //
@@ -173,6 +188,47 @@ typedef struct _IP6_HEADER {
     BYTE DestinationAddress[IP6_ADDRESS_SIZE];
 } PACKED IP6_HEADER, *PIP6_HEADER;
 
+/*++
+
+Structure Description:
+
+    This structure defines the header common to all IPv6 extension headers.
+
+Members:
+
+    NextHeader - Stores the type of the header following this extension header.
+
+    Length - Stores the length of the extension header, not including the first
+        8 bytes; all extension headers must be an integer multiple of 8 bytes.
+
+--*/
+
+typedef struct _IP6_EXTENSION_HEADER {
+    UCHAR NextHeader;
+    UCHAR Length;
+} PACKED IP6_EXTENSION_HEADER, *PIP6_EXTENSION_HEADER;
+
+/*++
+
+Structure Description:
+
+    This structure defines an IPv6 extension header option. The variable-length
+    option data immediately follows this structure.
+
+Members:
+
+    Type - Stores the type of IPv6 option. See IP6_OPTION_TYPE_* for
+        definitions.
+
+    Length - Stores the length of the IPv6 option, in bytes.
+
+--*/
+
+typedef struct _IP6_OPTION {
+    UCHAR Type;
+    UCHAR Length;
+} PACKED IP6_OPTION, *PIP6_OPTION;
+
 //
 // -------------------------------------------------------------------- Globals
 //
@@ -180,3 +236,4 @@ typedef struct _IP6_HEADER {
 //
 // -------------------------------------------------------- Function Prototypes
 //
+