Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1558 lines
50 KiB

// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// Internet Control Message Protocol for Internet Protocol Version 6.
// See RFC 1885 for details.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "route.h"
#include "icmp.h"
#include "ntddip6.h"
#include "neighbor.h"
#include "mld.h"
#include "security.h"
//
// Ping support. We have a list of EchoControl blocks, one per outstanding
// echo request message. Incoming echo replies are matched to requests via
// a unique sequence number.
//
KSPIN_LOCK ICMPv6EchoLock;
EchoControl *ICMPv6OutstandingEchos;
long ICMPv6EchoSeq; // Protected with interlocked operations.
//
// Statistics kept for netstat and MIBs.
//
ICMPv6Stats ICMPv6InStats;
ICMPv6Stats ICMPv6OutStats;
//* ICMPv6Init - Initialize ICMPv6.
//
// Set the starting values of various things.
//
void
ICMPv6Init(void)
{
//
// Initialize in-kernel ping support.
//
ICMPv6OutstandingEchos = NULL;
ICMPv6EchoSeq = 0;
KeInitializeSpinLock(&ICMPv6EchoLock);
//
// Initialize Multicast Listener Discovery protocol.
//
MLDInit();
}
//* ICMPv6Send - Low-level send routine for ICMPv6 packets.
//
// Common ICMPv6 message transmission functionality is performed here.
// The message is expected to be completely formed (with the exception
// of the checksum) when this routine is called.
//
// Used for all ICMP packets, except for Neighbor Discovery.
//
void
ICMPv6Send(
RouteCacheEntry *RCE, // RCE to send on
PNDIS_PACKET Packet, // Packet to send.
uint IPv6Offset, // Offset to IPv6 header in packet.
uint ICMPv6Offset, // Offset to ICMPv6 header in packet.
IPv6Header UNALIGNED *IP, // Pointer to IPv6 header.
uint PayloadLength, // Length of IPv6 payload in bytes.
ICMPv6Header UNALIGNED *ICMP) // Pointer to ICMPv6 header.
{
uint ChecksumDataLength;
ICMPv6OutStats.icmps_msgs++;
//
// Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message
// starting with the ICMPv6 header, plus the IPv6 pseudo-header.
//
// Recalculate the payload length to exclude any option headers.
//
ChecksumDataLength = PayloadLength -
(ICMPv6Offset - IPv6Offset) + sizeof(IPv6Header);
ICMP->Checksum = 0;
ICMP->Checksum = ChecksumPacket(Packet, ICMPv6Offset, NULL,
ChecksumDataLength,
AlignAddr(&IP->Source),
AlignAddr(&IP->Dest),
IP_PROTOCOL_ICMPv6);
if (ICMP->Checksum == 0) {
//
// ChecksumPacket failed, so abort the transmission.
//
ICMPv6OutStats.icmps_errors++;
IPv6SendComplete(NULL, Packet, IP_NO_RESOURCES);
return;
}
ICMPv6OutStats.icmps_typecount[ICMP->Type]++;
//
// Hand the packet down to IP for transmission.
//
IPv6Send(Packet, IPv6Offset, IP, PayloadLength, RCE, 0,
IP_PROTOCOL_ICMPv6, 0, 0);
}
//* ICMPv6SendEchoReply - Send an Echo Reply message.
//
// Basically what we do here is slap an ICMPv6 header on the front
// of the invoking packet and send it back where it came from.
//
void
ICMPv6SendEchoReply(
IPv6Packet *Packet) // Packet handed to us by ICMPv6Receive.
{
NDIS_STATUS NdisStatus;
PNDIS_PACKET ReplyPacket;
uint Offset;
uchar *Mem;
uint MemLen;
uint ICMPLength;
uint DataLength;
IPv6Header UNALIGNED *ReplyIP;
ICMPv6Header UNALIGNED *ReplyICMP;
const IPv6Addr *Dest;
IP_STATUS Status;
RouteCacheEntry *RCE;
//
// Take our reply's destination address from the source address
// of the incoming packet.
//
// Note that the specs specifically say that we're not to reverse
// the path on source routed packets. Just reply directly.
//
// IPv6HeaderReceive should protect us from replying to most forms
// of bogus addresses. We ASSERT this in checked builds.
//
Dest = Packet->SrcAddr;
ASSERT(!IsInvalidSourceAddress(Dest));
//
// Get the reply route to the destination.
// Under normal circumstances, the reply will go out
// the incoming interface. RouteToDestination
// will figure out the appropriate ScopeId.
//
Status = RouteToDestination(Dest, 0, Packet->NTEorIF,
RTD_FLAG_NORMAL, &RCE);
if (Status != IP_SUCCESS) {
//
// No route - drop the packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"ICMPv6SendEchoReply - no route: %x\n", Status));
return;
}
//
// Calculate the length of the ICMP header
// and how much data we will include following the ICMP header.
//
ICMPLength = sizeof(ICMPv6Header);
DataLength = Packet->TotalSize;
Offset = RCE->NCE->IF->LinkHeaderSize;
MemLen = Offset + sizeof(IPv6Header) + ICMPLength + DataLength;
//
// Allocate the reply packet.
//
NdisStatus = IPv6AllocatePacket(MemLen, &ReplyPacket, &Mem);
if (NdisStatus != NDIS_STATUS_SUCCESS) {
ReleaseRCE(RCE);
return;
}
//
// Prepare IP header of reply packet.
//
ReplyIP = (IPv6Header UNALIGNED *)(Mem + Offset);
ReplyIP->VersClassFlow = IP_VERSION;
ReplyIP->NextHeader = IP_PROTOCOL_ICMPv6;
ReplyIP->HopLimit = (uchar)RCE->NCE->IF->CurHopLimit;
//
// Take our reply's source address from the receiving NTE,
// or use the best source address for this destination
// if we don't have a receiving NTE.
//
ReplyIP->Source = (IsNTE(Packet->NTEorIF) ?
CastToNTE(Packet->NTEorIF) : RCE->NTE)
->Address;
ReplyIP->Dest = *Dest;
//
// Prepare ICMP header.
//
// REVIEW: Do this in ICMPv6Send?
//
ReplyICMP = (ICMPv6Header UNALIGNED *)(ReplyIP + 1);
ReplyICMP->Type = ICMPv6_ECHO_REPLY;
ReplyICMP->Code = 0;
// ReplyICMP->Checksum - ICMPv6Send will calculate.
//
// Copy incoming packet data to outgoing.
//
CopyPacketToBuffer((uchar *)(ReplyICMP + 1), Packet, DataLength,
Packet->Position);
ICMPv6Send(RCE, ReplyPacket, Offset,
Offset + sizeof(IPv6Header), ReplyIP,
ICMPLength + DataLength, ReplyICMP);
ReleaseRCE(RCE);
}
//* ICMPv6CheckError
//
// Check if a packet is an ICMP error message.
// This is a "best effort" check, given that
// the packet may well have syntactical errors.
//
// We return FALSE if we can't tell.
//
int
ICMPv6CheckError(IPv6Packet *Packet, uint NextHeader)
{
for (;;) {
uint HdrLen;
switch (NextHeader) {
case IP_PROTOCOL_HOP_BY_HOP:
case IP_PROTOCOL_DEST_OPTS:
case IP_PROTOCOL_ROUTING: {
ExtensionHeader *Hdr;
if (! PacketPullup(Packet, sizeof *Hdr,
__builtin_alignof(ExtensionHeader), 0)) {
//
// Pullup failed. We can't continue parsing.
//
return FALSE;
}
Hdr = (ExtensionHeader *) Packet->Data;
HdrLen = (Hdr->HeaderExtLength + 1) * EXT_LEN_UNIT;
//
// REVIEW - We don't actually want to look at the remaining
// data in the extension header. Perhaps use PositionPacketAt?
//
if (! PacketPullup(Packet, HdrLen, 1, 0)) {
//
// Pullup failed. We can't continue parsing.
//
return FALSE;
}
NextHeader = Hdr->NextHeader;
break;
}
case IP_PROTOCOL_FRAGMENT: {
FragmentHeader UNALIGNED *Hdr;
if (! PacketPullup(Packet, sizeof *Hdr, 1, 0)) {
//
// Pullup failed. We can't continue parsing.
//
return FALSE;
}
Hdr = (FragmentHeader UNALIGNED *) Packet->Data;
//
// We can only continue parsing if this is the first fragment.
//
if ((Hdr->OffsetFlag & FRAGMENT_OFFSET_MASK) != 0)
return FALSE;
HdrLen = sizeof *Hdr;
NextHeader = Hdr->NextHeader;
break;
}
case IP_PROTOCOL_ICMPv6: {
ICMPv6Header *Hdr;
if (! PacketPullup(Packet, sizeof *Hdr,
__builtin_alignof(ICMPv6Header), 0)) {
//
// Pullup failed. We can't continue parsing.
//
return FALSE;
}
//
// This is an ICMPv6 message, so we can check
// to see if it is an error message.
// We treat Redirects as errors here.
//
Hdr = (ICMPv6Header *) Packet->Data;
return (ICMPv6_ERROR_TYPE(Hdr->Type) ||
(Hdr->Type == ICMPv6_REDIRECT));
}
default:
return FALSE;
}
//
// Move past this extension header.
//
AdjustPacketParams(Packet, HdrLen);
}
}
//* ICMPv6RateLimit
//
// Returns TRUE if an ICMP error should NOT be sent to this destination
// because of rate-limiting.
//
int
ICMPv6RateLimit(RouteCacheEntry *RCE)
{
uint Now = IPv6TickCount;
//
// This arithmetic will handle wraps of the IPv6 tick counter.
//
if ((uint)(Now - RCE->LastError) < ICMP_MIN_ERROR_INTERVAL)
return TRUE;
RCE->LastError = Now;
return FALSE;
}
//* ICMPv6SendError - Generate an error in response to an incoming packet.
//
// Send an ICMPv6 message of the given Type and Code to the source of the
// offending/invoking packet. The reply includes as much of the incoming
// packet as will fit inside the minimal IPv6 MTU.
//
// Basically what we do here is slap an ICMPv6 header on the front
// of the invoking packet and send it back where it came from.
//
// REVIEW - Much of the code looks like ICMPv6SendEchoReply.
// Could it be shared?
//
// The current position in the Packet must be at a header boundary.
// The NextHeader parameter specifies the type of header following.
// This information is used to parse the remainder of the invoking Packet,
// to see if it is an ICMP error. We MUST avoid sending an error
// in response to an error. NextHeader may be IP_PROTOCOL_NONE.
//
// The MulticastOverride parameter allows override of another check.
// Normally we MUST avoid sending an error in response to a packet
// sent to a multicast destination. But there are a couple exceptions.
//
void
ICMPv6SendError(
IPv6Packet *Packet, // Offending/Invoking packet.
uchar ICMPType, // ICMP error type.
uchar ICMPCode, // ICMP error code.
ulong ErrorParameter, // Parameter for error message.
uint NextHeader, // Type of hdr following in Packet.
int MulticastOverride) // Allow replies to mcast packets?
{
NDIS_STATUS NdisStatus;
PNDIS_PACKET ReplyPacket;
uint Offset;
uchar *Mem;
uint MemLen;
uint ICMPLength;
uint DataLength;
IPv6Header UNALIGNED *ReplyIP;
ICMPv6Header UNALIGNED *ReplyICMP;
const IPv6Addr *Dest;
IP_STATUS Status;
RouteCacheEntry *RCE;
//
// We must not send an ICMP error message
// as a result of an ICMP error.
//
if ((Packet->Flags & PACKET_ICMP_ERROR) ||
ICMPv6CheckError(Packet, NextHeader)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6SendError: no reply to error\n"));
return;
}
//
// We must not send an ICMP error message as a result
// of receiving any kind of multicast or broadcast.
// There are a couple exceptions so we have MulticastOverride.
//
if (IsMulticast(AlignAddr(&Packet->IP->Dest)) ||
(Packet->Flags & PACKET_NOT_LINK_UNICAST)) {
if (!MulticastOverride) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6SendError: no reply to broadcast/multicast\n"));
return;
}
}
//
// Take our reply's destination address from the source address
// of the incoming packet.
//
// Note that the specs specifically say that we're not to reverse
// the path on source routed packets. Just reply directly.
//
// IPv6HeaderReceive should protect us from replying to most forms
// of bogus addresses. We ASSERT this in checked builds.
//
Dest = Packet->SrcAddr;
ASSERT(!IsInvalidSourceAddress(Dest));
//
// Get the reply route to the destination.
// Under normal circumstances, the reply will go out
// the incoming interface. RouteToDestination
// will figure out the appropriate ScopeId.
//
Status = RouteToDestination(Dest, 0, Packet->NTEorIF,
RTD_FLAG_NORMAL, &RCE);
if (Status != IP_SUCCESS) {
//
// No route - drop the packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"ICMPv6SendError - no route: %x\n", Status));
return;
}
//
// We must rate-limit ICMP error messages.
//
if (ICMPv6RateLimit(RCE)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6SendError - rate limit %s\n",
FormatV6Address(&RCE->Destination)));
ReleaseRCE(RCE);
return;
}
//
// Calculate the length of the ICMP header
// and how much data we will include following the ICMP header.
// Include space for an error value after the header proper.
//
ICMPLength = sizeof(ICMPv6Header) + sizeof(uint);
//
// We want to include data from the IP header on.
//
DataLength = Packet->TotalSize +
(Packet->Position - Packet->IPPosition);
//
// But limit the error packet size.
//
if (DataLength > ICMPv6_ERROR_MAX_DATA_LEN)
DataLength = ICMPv6_ERROR_MAX_DATA_LEN;
//
// Calculate buffer length.
//
Offset = RCE->NCE->IF->LinkHeaderSize;
MemLen = Offset + sizeof(IPv6Header) + ICMPLength + DataLength;
ASSERT(MemLen - Offset <= IPv6_MINIMUM_MTU);
//
// Allocate the reply packet.
//
NdisStatus = IPv6AllocatePacket(MemLen, &ReplyPacket, &Mem);
if (NdisStatus != NDIS_STATUS_SUCCESS) {
ReleaseRCE(RCE);
return;
}
//
// Prepare IP header of reply packet.
//
ReplyIP = (IPv6Header UNALIGNED *)(Mem + Offset);
ReplyIP->VersClassFlow = IP_VERSION;
ReplyIP->NextHeader = IP_PROTOCOL_ICMPv6;
ReplyIP->HopLimit = (uchar)RCE->NCE->IF->CurHopLimit;
//
// Take our reply's source address from the receiving NTE,
// or use the best source address for this destination
// if we don't have a receiving NTE.
//
ReplyIP->Source = (IsNTE(Packet->NTEorIF) ?
CastToNTE(Packet->NTEorIF) : RCE->NTE)
->Address;
ReplyIP->Dest = *Dest;
//
// Prepare ICMP header.
//
// REVIEW: Do this in ICMPv6Send?
//
ReplyICMP = (ICMPv6Header UNALIGNED *)(ReplyIP + 1);
ReplyICMP->Type = ICMPType;
ReplyICMP->Code = ICMPCode;
// ReplyICMP->Checksum - ICMPv6Send will calculate.
//
// ICMP Error Messages have a 32-bit field (content of which
// varies depending upon the error type) following the ICMP header.
//
*(ulong UNALIGNED *)(ReplyICMP + 1) = net_long(ErrorParameter);
//
// Copy invoking packet (from IPv6 header onward) to outgoing.
//
CopyPacketToBuffer((uchar *)(ReplyICMP + 1) + sizeof(ErrorParameter),
Packet, DataLength, Packet->IPPosition);
ICMPv6Send(RCE, ReplyPacket, Offset,
Offset + sizeof(IPv6Header), ReplyIP,
ICMPLength + DataLength, ReplyICMP);
ReleaseRCE(RCE);
}
//* ICMPv6ProcessTunnelError
//
// Called when we receive an ICMPv4 error and there is insufficient
// information to translate to an ICMPv6 error. We make a best effort
// to complete outstanding echo requests that were sent to the IPv4
// address that was the original IPv4 tunnel destination.
//
void
ICMPv6ProcessTunnelError(
IPAddr V4Dest, // Destination of our tunneled packet.
IPv6Addr *V6Src, // Address to use as the source of the error.
uint ScopeId, // Scope-id of V6Src.
IP_STATUS Status) // Status of the response.
{
EchoControl *This, **PrevPtr;
EchoControl *List = NULL;
KIRQL OldIrql;
//
// Find the EchoControl blocks on our list of outstanding echoes that
// have a matching IPv4 destination and call their completion function.
// We do not have sufficient information to identify a unique request.
//
KeAcquireSpinLock(&ICMPv6EchoLock, &OldIrql);
PrevPtr = &ICMPv6OutstandingEchos;
while ((This = *PrevPtr) != NULL) {
if (This->V4Dest == V4Dest) {
//
// Found matching control block. Extract it from the list
// and put it on our own list.
//
*PrevPtr = This->Next;
This->Next = List;
List = This;
}
else
PrevPtr = &This->Next;
}
KeReleaseSpinLock(&ICMPv6EchoLock, OldIrql);
while ((This = List) != NULL) {
//
// Remove this request from our list.
//
List = This->Next;
//
// Call OS-specific completion routine.
//
(*This->CompleteRoutine)(This, Status,
V6Src, ScopeId,
NULL, 0);
}
}
//* ICMPv6ProcessEchoReply
//
// Called either when an echo reply arrives, or
// a hop-count-exceeded error responding to an echo request arrives.
//
// Looks up the echo request structure and completes
// the echo request operation.
//
// Note that the echo reply payload data must be contiguous.
// Callers should use PacketPullup if necessary.
//
void
ICMPv6ProcessEchoReply(
ulong Seq, // Echo sequence number.
IP_STATUS Status, // Status of the response.
IPv6Packet *Packet, // Echo reply packet.
void *Current, // Pointer to the buffered data area.
uint PayloadLength) // Size of remaining payload data.
{
EchoControl *This, **PrevPtr;
KIRQL OldIrql;
uint ICMPPosition;
//
// Find the EchoControl block on our list of outstanding echoes that
// has a matching sequence number and call it's completion function.
//
KeAcquireSpinLock(&ICMPv6EchoLock, &OldIrql);
PrevPtr = &ICMPv6OutstandingEchos;
while ((This = *PrevPtr) != NULL) {
if (This->Seq == Seq) {
//
// Found matching control block. Extract it from list.
//
*PrevPtr = This->Next;
break;
}
PrevPtr = &This->Next;
}
KeReleaseSpinLock(&ICMPv6EchoLock, OldIrql);
//
// Check to see if we ran off the end of the outstanding echoes list.
//
if (This == NULL) {
//
// We received a response with a sequence number that doesn't match
// one of the echo requests we still have outstanding. Drop it.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6ProcessEchoReply: Received echo response "
"with bogus/expired sequence number 0x%x\n", Seq));
if (Current != NULL) {
//
// If this is a normal Echo Reply (not a error message sent in
// response to one of our Echo Replies) first see if any raw
// receivers want to look at it.
//
ICMPPosition = Packet->Position - sizeof(ICMPv6Header)
- sizeof(Seq);
PositionPacketAt(Packet, ICMPPosition);
(void) RawReceive(Packet, IP_PROTOCOL_ICMPv6);
}
return;
}
//
// Call OS-specific completion routine.
//
(*This->CompleteRoutine)(This, Status,
Packet->SrcAddr,
DetermineScopeId(Packet->SrcAddr,
Packet->NTEorIF->IF),
Current, PayloadLength);
}
//* ICMPv6EchoReplyReceive - Receive a reply to an earlier echo of our's.
//
// Called by ICMPv6Receive when an echo reply message arrives.
//
// REVIEW: Should we also verify the receiving NTE is the same as the one
// REVIEW: we sent on?
//
void
ICMPv6EchoReplyReceive(IPv6Packet *Packet)
{
ulong Seq;
//
// The next four bytes should consist of a two byte Identifier field
// and a two byte Sequence Number. We just treat the whole thing as
// a four byte sequence number. Make sure these bytes are contiguous.
//
if (! PacketPullup(Packet, sizeof Seq, 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof(Seq)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6: Received small Echo Reply %u\n",
Packet->TotalSize));
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
IP_PROTOCOL_NONE, FALSE);
}
return; // Drop packet.
}
//
// We're received a reply message to one of our echo requests.
// Extract its sequence number so that we can identify it.
//
Seq = net_long(*(ulong UNALIGNED *)Packet->Data);
AdjustPacketParams(Packet, sizeof Seq);
//
// REVIEW: The ICMPv6ProcessEchoReply interface expects a contiguous data
// REVIEW: region for the rest of the packet. This requires us to
// REVIEW: pullup the remainder of the packet here. Fix this someday.
//
if (! PacketPullup(Packet, Packet->TotalSize, 1, 0)) {
// Pullup failed.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"ICMPv6: Couldn't pullup echo data\n"));
return; // Drop packet.
}
ICMPv6ProcessEchoReply(Seq, IP_SUCCESS, Packet,
Packet->Data, Packet->TotalSize);
}
//* ICMPv6ErrorReceive - Generic ICMPv6 error processing.
//
// Called by ICMPv6Receive when an error message arrives.
// Returns FALSE if we were unable to process it for some reason.
//
int
ICMPv6ErrorReceive(
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
ICMPv6Header UNALIGNED *ICMP) // ICMP Header.
{
ulong Parameter;
IP_STATUS Status;
StatusArg StatArg;
IPv6Header UNALIGNED *InvokingIP;
ProtoControlRecvProc *Handler = NULL;
uchar NextHeader;
int Handled = TRUE;
//
// First mark the packet as an ICMP error.
// This will inhibit any generation of ICMP errors
// as a result of this packet.
//
Packet->Flags |= PACKET_ICMP_ERROR;
//
// All ICMPv6 error messages consist of the base ICMPv6 header,
// followed by a 32 bit type-specific field, followed by as much
// of the invoking packet as fit without causing this ICMPv6 packet
// to exceed 576 octets.
//
// We already consumed the base ICMPv6 header back in ICMPv6Receive.
// Pull out the 32 bit type-specific field in case the upper layer's
// ControlReceive routine cares about it.
//
if (! PacketPullup(Packet, sizeof Parameter, 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof Parameter)
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6ErrorReceive: "
"Packet too small to contain error field\n"));
return FALSE; // Drop packet.
}
Parameter = *(ulong UNALIGNED *)Packet->Data;
AdjustPacketParams(Packet, sizeof Parameter);
//
// Next up should be the IPv6 header of the invoking packet.
//
if (! PacketPullup(Packet, sizeof *InvokingIP,
__builtin_alignof(IPv6Addr), 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof *InvokingIP)
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6ErrorReceive (from %s): "
"Packet too small to contain IPv6 "
"header from invoking packet\n",
FormatV6Address(AlignAddr(&Packet->IP->Source))));
return FALSE; // Drop packet.
}
InvokingIP = (IPv6Header UNALIGNED *)Packet->Data;
AdjustPacketParams(Packet, sizeof *InvokingIP);
//
// First we perform any specific processing of the error,
// and convert the error type/code to a status value.
//
switch (ICMP->Type) {
case ICMPv6_DESTINATION_UNREACHABLE:
switch (ICMP->Code) {
case ICMPv6_NO_ROUTE_TO_DESTINATION:
Status = IP_DEST_NO_ROUTE;
break;
case ICMPv6_COMMUNICATION_PROHIBITED:
Status = IP_DEST_PROHIBITED;
break;
case ICMPv6_SCOPE_MISMATCH:
Status = IP_DEST_SCOPE_MISMATCH;
break;
case ICMPv6_ADDRESS_UNREACHABLE:
Status = IP_DEST_ADDR_UNREACHABLE;
break;
case ICMPv6_PORT_UNREACHABLE:
Status = IP_DEST_PORT_UNREACHABLE;
break;
default:
Status = IP_DEST_UNREACHABLE;
break;
}
break;
case ICMPv6_PACKET_TOO_BIG: {
uint PMTU;
//
// Packet Too Big messages contain the bottleneck MTU value.
// Update the path MTU in the route cache.
// Change Parameter value to indicate whether PMTU changed.
//
PMTU = net_long(Parameter);
Parameter = UpdatePathMTU(Packet->NTEorIF->IF,
AlignAddr(&InvokingIP->Dest), PMTU);
Status = IP_PACKET_TOO_BIG;
break;
}
case ICMPv6_TIME_EXCEEDED:
switch (ICMP->Code) {
case ICMPv6_HOP_LIMIT_EXCEEDED:
Status = IP_HOP_LIMIT_EXCEEDED;
break;
case ICMPv6_REASSEMBLY_TIME_EXCEEDED:
Status = IP_REASSEMBLY_TIME_EXCEEDED;
break;
default:
Status = IP_TIME_EXCEEDED;
break;
}
break;
case ICMPv6_PARAMETER_PROBLEM:
switch (ICMP->Code) {
case ICMPv6_ERRONEOUS_HEADER_FIELD:
Status = IP_BAD_HEADER;
break;
case ICMPv6_UNRECOGNIZED_NEXT_HEADER:
Status = IP_UNRECOGNIZED_NEXT_HEADER;
break;
case ICMPv6_UNRECOGNIZED_OPTION:
Status = IP_BAD_OPTION;
break;
default:
Status = IP_PARAMETER_PROBLEM;
break;
}
break;
default:
//
// We don't understand this error type.
//
Status = IP_ICMP_ERROR;
Handled = FALSE;
break;
}
//
// Deliver ICMP Error to higher layers. This is a MUST, even if we
// don't recognize the specific error message.
//
// Iteratively switch out to the handler for each successive next header
// until we reach a handler that reports no more headers follow it.
//
NextHeader = InvokingIP->NextHeader;
while (NextHeader != IP_PROTOCOL_NONE) {
//
// Current header indicates that another header follows.
// See if we have a handler for it.
//
Handler = ProtocolSwitchTable[NextHeader].ControlReceive;
if (Handler == NULL) {
//
// If we don't have a handler for this header type,
// we just drop the packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6ErrorReceive: No handler for NextHeader type %u.\n",
NextHeader));
break;
}
StatArg.Status = Status;
StatArg.Arg = Parameter;
StatArg.IP = InvokingIP;
NextHeader = (*Handler)(Packet, &StatArg);
}
return Handled;
}
//* ICMPv6ControlReceive - handler for ICMPv6 control messages.
//
// This routine is called if we receive an ICMPv6 error message that
// was generated by some remote site as a result of receiving an ICMPv6
// packet from us.
//
uchar
ICMPv6ControlReceive(
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
StatusArg *StatArg) // ICMP Error Code, etc.
{
ICMPv6Header *InvokingICMP;
ulong Seq;
//
// The next thing in the packet should be the ICMP header of the
// original packet which invoked this error.
//
if (! PacketPullup(Packet, sizeof *InvokingICMP,
__builtin_alignof(ICMPv6Header), 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof *InvokingICMP)
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6: Packet too small to contain ICMPv6 header "
"from invoking packet\n"));
return IP_PROTOCOL_NONE; // Drop packet.
}
InvokingICMP = (ICMPv6Header *)Packet->Data;
AdjustPacketParams(Packet, sizeof *InvokingICMP);
//
// All we currently handle is errors caused by echo requests.
//
if ((InvokingICMP->Type != ICMPv6_ECHO_REQUEST) ||
(InvokingICMP->Code != 0))
return IP_PROTOCOL_NONE; // Drop packet.
//
// The next four bytes should consist of a two byte Identifier field
// and a two byte Sequence Number. We just treat the whole thing as
// a four byte sequence number. Make sure these bytes are contiguous.
//
if (! PacketPullup(Packet, sizeof Seq, 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof Seq)
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6: Packet too small to contain ICMPv6 header "
"from invoking packet\n"));
return IP_PROTOCOL_NONE; // Drop packet.
}
//
// Extract the sequence number so that we can identify
// the matching echo request.
//
Seq = net_long(*(ulong UNALIGNED *)Packet->Data);
AdjustPacketParams(Packet, sizeof Seq);
//
// Complete the corresponding echo request with an error.
//
ICMPv6ProcessEchoReply(Seq, StatArg->Status, Packet,
NULL, 0);
return IP_PROTOCOL_NONE; // Done with packet.
}
//* ICMPv6Receive - Receive an incoming ICMPv6 packet.
//
// This is the routine called by IPv6 when it receives a complete IPv6
// packet with a Next Header value of 58.
//
uchar
ICMPv6Receive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
ICMPv6Header *ICMP;
ushort Checksum;
uint ICMPPosition;
ICMPv6InStats.icmps_msgs++;
//
// Verify IPSec was performed.
//
if (InboundSecurityCheck(Packet, IP_PROTOCOL_ICMPv6, 0, 0,
Packet->NTEorIF->IF) != TRUE) {
//
// No policy was found or the policy indicated to drop the packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6: IPSec lookup failed\n"));
ICMPv6InStats.icmps_errors++;
return IP_PROTOCOL_NONE; // Drop packet.
}
//
// Verify that we have enough contiguous data to overlay a ICMPv6Header
// structure on the incoming packet. Then do so.
//
if (! PacketPullup(Packet, sizeof *ICMP,
__builtin_alignof(ICMPv6Header), 0)) {
// Pullup failed.
ICMPv6InStats.icmps_errors++;
if (Packet->TotalSize < sizeof *ICMP) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6: Packet too small to contain ICMP header\n"));
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
IP_PROTOCOL_NONE, FALSE);
}
return IP_PROTOCOL_NONE; // Drop packet.
}
ICMP = (ICMPv6Header *)Packet->Data;
ICMPPosition = Packet->Position;
//
// Verify checksum.
//
Checksum = ChecksumPacket(Packet->NdisPacket, Packet->Position,
Packet->FlatData, Packet->TotalSize,
Packet->SrcAddr, AlignAddr(&Packet->IP->Dest),
IP_PROTOCOL_ICMPv6);
if (Checksum != 0xffff) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6: Checksum failed %0x\n", Checksum));
ICMPv6InStats.icmps_errors++;
return IP_PROTOCOL_NONE; // Drop packet.
}
//
// Skip over base ICMP header.
//
AdjustPacketParams(Packet, sizeof *ICMP);
//
// Ignore Neighbor Discovery packets
// if the interface is so configured.
// (Pseudo-interfaces don't do Neighbor Discovery.)
//
if (!(Packet->NTEorIF->IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS)) {
if ((ICMP->Type == ICMPv6_NEIGHBOR_SOLICIT) ||
(ICMP->Type == ICMPv6_NEIGHBOR_ADVERT)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6Receive: ND on pseudo-interface\n"));
ICMPv6InStats.icmps_errors++;
return IP_PROTOCOL_NONE; // Drop packet.
}
}
//
// Ignore Router Discovery packets
// if the interface is so configured.
//
if (!(Packet->NTEorIF->IF->Flags & IF_FLAG_ROUTER_DISCOVERS)) {
if ((ICMP->Type == ICMPv6_ROUTER_SOLICIT) ||
(ICMP->Type == ICMPv6_ROUTER_ADVERT) ||
(ICMP->Type == ICMPv6_REDIRECT)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6Receive: RD on pseudo-interface\n"));
ICMPv6InStats.icmps_errors++;
return IP_PROTOCOL_NONE; // Drop packet.
}
}
ICMPv6InStats.icmps_typecount[ICMP->Type]++;
//
// We have a separate routine to handle error messages.
//
if (ICMPv6_ERROR_TYPE(ICMP->Type)) {
if (!ICMPv6ErrorReceive(Packet, ICMP))
goto unrecognized;
return IP_PROTOCOL_NONE;
}
//
// Handle specific informational message types.
// Just use a switch statement for now. If this is later deemed to be
// too inefficient, we can change it to use a type switch table instead.
//
switch(ICMP->Type) {
case ICMPv6_ECHO_REQUEST:
ICMPv6SendEchoReply(Packet);
break;
case ICMPv6_ECHO_REPLY:
ICMPv6EchoReplyReceive(Packet);
break;
case ICMPv6_MULTICAST_LISTENER_QUERY:
MLDQueryReceive(Packet);
break;
case ICMPv6_MULTICAST_LISTENER_REPORT:
MLDReportReceive(Packet);
break;
case ICMPv6_MULTICAST_LISTENER_DONE:
break;
// Following are all Neighbor Discovery messages.
case ICMPv6_ROUTER_SOLICIT:
RouterSolicitReceive(Packet, ICMP);
break;
case ICMPv6_ROUTER_ADVERT:
RouterAdvertReceive(Packet, ICMP);
break;
case ICMPv6_NEIGHBOR_SOLICIT:
NeighborSolicitReceive(Packet, ICMP);
break;
case ICMPv6_NEIGHBOR_ADVERT:
NeighborAdvertReceive(Packet, ICMP);
break;
case ICMPv6_REDIRECT:
RedirectReceive(Packet, ICMP);
break;
default:
//
// Don't recognize the specific message type.
// This is an unknown informational message.
// We MUST silently discard it.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ICMPv6: Received unknown informational message"
"(%u/%u) from %s\n", ICMP->Type, ICMP->Code,
FormatV6Address(AlignAddr(&Packet->IP->Source))));
//
// But first see if any raw receivers want to look at it.
// NOTE: We don't get any feedback from raw receivers,
// NOTE: so we can't tell if any of them knew this type.
//
unrecognized:
PositionPacketAt(Packet, ICMPPosition);
(void) RawReceive(Packet, IP_PROTOCOL_ICMPv6);
break;
}
return IP_PROTOCOL_NONE;
}
//* ICMPv6EchoRequest - Common dispatch routine for echo requests.
//
// This is the routine called by the OS-specific code on behalf of a user
// to issue an echo request. Validate the request, place control block
// on list of outstanding echo requests, and send echo request message.
//
void
ICMPv6EchoRequest(
void *InputBuffer, // Pointer to an ICMPV6_ECHO_REQUEST structure.
uint InputBufferLength, // Size in bytes of the InputBuffer.
EchoControl *ControlBlock, // Pointer to an EchoControl structure.
EchoRtn Callback) // Called when request responds or times out.
{
NetTableEntry *NTE = NULL;
PICMPV6_ECHO_REQUEST RequestBuffer;
KIRQL OldIrql;
IP_STATUS Status;
ulong Seq;
NDIS_STATUS NdisStatus;
PNDIS_PACKET Packet;
uint Offset;
uchar *Mem;
uint MemLen;
IPv6Header UNALIGNED *IP;
ICMPv6Header UNALIGNED *ICMP;
void *Data;
uint DataSize;
uint RtHdrSize;
RouteCacheEntry *RCE = NULL;
const IPv6Addr *FinalDest, *FirstDest;
const IPv6Addr *DstAddress, *SrcAddress;
uint DstScopeId, SrcScopeId;
ICMPv6OutStats.icmps_msgs++;
RequestBuffer = (PICMPV6_ECHO_REQUEST) InputBuffer;
//
// Validate the request.
//
if (InputBufferLength < sizeof *RequestBuffer) {
Status = IP_BUF_TOO_SMALL;
goto common_echo_exit;
}
//
// If InputBufferLength is too big, it could cause a
// buffer overflow later on in the computation for
// MemLen. Cap the value to MAXLONG.
//
if (InputBufferLength > (uint) MAXLONG) {
Status = IP_PARAM_PROBLEM;
goto common_echo_exit;
}
Data = RequestBuffer + 1;
DataSize = InputBufferLength - sizeof *RequestBuffer;
//
// Extract address information from the TDI addresses
// in the request.
//
DstAddress = (const IPv6Addr *) RequestBuffer->DstAddress.sin6_addr;
DstScopeId = RequestBuffer->DstAddress.sin6_scope_id;
SrcAddress = (const IPv6Addr *) RequestBuffer->SrcAddress.sin6_addr;
SrcScopeId = RequestBuffer->SrcAddress.sin6_scope_id;
//
// Determine which NTE will send the request,
// if the user has specified a source address.
//
if (! IsUnspecified(SrcAddress)) {
//
// Convert the source address to an NTE.
//
NTE = FindNetworkWithAddress(SrcAddress, SrcScopeId);
if (NTE == NULL) {
Status = IP_BAD_ROUTE;
goto common_echo_exit;
}
Status = RouteToDestination(DstAddress, DstScopeId,
CastFromNTE(NTE),
RTD_FLAG_NORMAL, &RCE);
if (Status != IP_SUCCESS)
goto common_echo_exit;
} else {
//
// Get the source address from the outgoing interface.
//
Status = RouteToDestination(DstAddress, DstScopeId,
NULL,
RTD_FLAG_NORMAL, &RCE);
if (Status != IP_SUCCESS)
goto common_echo_exit;
NTE = RCE->NTE;
AddRefNTE(NTE);
}
//
// Should we use a routing header to send
// a "round-trip" echo request to ourself?
//
if (RequestBuffer->Flags & ICMPV6_ECHO_REQUEST_FLAG_REVERSE) {
//
// Use a routing header.
//
FinalDest = &NTE->Address;
FirstDest = DstAddress;
RtHdrSize = sizeof(IPv6RoutingHeader) + sizeof(IPv6Addr);
}
else {
//
// No routing header.
//
FinalDest = FirstDest = DstAddress;
RtHdrSize = 0;
}
//
// Allocate the Echo Request packet.
//
Offset = RCE->NCE->IF->LinkHeaderSize;
MemLen = Offset + sizeof(IPv6Header) + RtHdrSize + sizeof(ICMPv6Header) +
sizeof Seq + DataSize;
NdisStatus = IPv6AllocatePacket(MemLen, &Packet, &Mem);
if (NdisStatus != NDIS_STATUS_SUCCESS) {
Status = IP_NO_RESOURCES;
goto common_echo_exit;
}
//
// Prepare IP header of Echo Request packet.
//
IP = (IPv6Header UNALIGNED *)(Mem + Offset);
IP->VersClassFlow = IP_VERSION;
IP->NextHeader = IP_PROTOCOL_ICMPv6;
IP->Source = NTE->Address;
IP->Dest = *FirstDest;
IP->HopLimit = RequestBuffer->TTL;
if (IP->HopLimit == 0)
IP->HopLimit = (uchar)RCE->NCE->IF->CurHopLimit;
//
// Prepare the routing header.
// The packet will travel to the destination and then
// be routed back to the source.
//
if (RtHdrSize != 0) {
IPv6RoutingHeader *RtHdr = (IPv6RoutingHeader *)(IP + 1);
IP->NextHeader = IP_PROTOCOL_ROUTING;
RtHdr->NextHeader = IP_PROTOCOL_ICMPv6;
RtHdr->HeaderExtLength = 2;
RtHdr->RoutingType = 0;
RtHdr->SegmentsLeft = 1;
RtlZeroMemory(&RtHdr->Reserved, sizeof RtHdr->Reserved);
((IPv6Addr *)(RtHdr + 1))[0] = *FinalDest;
}
//
// Prepare ICMP header.
//
ICMP = (ICMPv6Header UNALIGNED *)
((uchar *)IP + sizeof(IPv6Header) + RtHdrSize);
ICMP->Type = ICMPv6_ECHO_REQUEST;
ICMP->Code = 0;
ICMP->Checksum = 0; // Calculated below.
//
// Insert Echo sequence number. Technically, this is 16 bits of
// "Identifier" and 16 bits of "Sequence Number", but we just treat
// the whole thing as one 32 bit sequence number field.
//
Seq = InterlockedIncrement(&ICMPv6EchoSeq);
Mem = (uchar *)(ICMP + 1);
*(ulong UNALIGNED *)Mem = net_long(Seq);
Mem += sizeof(ulong);
//
// Copy the user data into the packet.
//
RtlCopyMemory(Mem, Data, DataSize);
//
// We calculate the checksum here, because
// of routing header complications -
// we need to use the final destination.
//
ICMP->Checksum = ChecksumPacket(
NULL, 0, (uchar *)ICMP, sizeof(ICMPv6Header) + sizeof Seq + DataSize,
AlignAddr(&IP->Source), FinalDest, IP_PROTOCOL_ICMPv6);
if (ICMP->Checksum == 0) {
//
// ChecksumPacket failed, so abort the transmission.
//
IPv6FreePacket(Packet);
Status = IP_NO_RESOURCES;
goto common_echo_exit;
}
//
// If this Echo Request is being tunneled to an IPv4 destination,
// remember the IPv4 destination address. We use this later
// if we receive an ICMPv4 error with insufficient information
// to translate to an ICMPv6 error.
//
ControlBlock->V4Dest = GetV4Destination(RCE);
//
// Prepare the control block and link it onto the list.
// Once we've unlocked the list, the control block might
// be completed at any time. Hence it's very important
// that we not access RequestBuffer after this point.
// Also we can not return a failure code. To clean up the
// outstanding request properly, we must use ICMPv6ProcessEchoReply.
//
ControlBlock->TimeoutTimer = ConvertMillisToTicks(RequestBuffer->Timeout);
ControlBlock->CompleteRoutine = Callback;
ControlBlock->Seq = Seq;
if (ControlBlock->TimeoutTimer == 0) {
IPv6FreePacket(Packet);
Status = IP_REQ_TIMED_OUT;
goto common_echo_exit;
}
KeAcquireSpinLock(&ICMPv6EchoLock, &OldIrql);
ControlBlock->Next = ICMPv6OutstandingEchos;
ICMPv6OutstandingEchos = ControlBlock;
KeReleaseSpinLock(&ICMPv6EchoLock, OldIrql);
ICMPv6OutStats.icmps_typecount[ICMPv6_ECHO_REQUEST]++;
//
// Hand the packet down to IP for transmission.
// We can't use ICMPv6Send
// because of routing header complications.
//
IPv6Send(Packet, Offset, IP,
RtHdrSize + sizeof(ICMPv6Header) + sizeof Seq + DataSize,
RCE, 0, IP_PROTOCOL_ICMPv6, 0, 0);
common_echo_cleanup:
if (RCE != NULL)
ReleaseRCE(RCE);
if (NTE != NULL)
ReleaseNTE(NTE);
return;
common_echo_exit:
//
// Complete the echo request with an error,
// before it has been placed on our outstanding echoes list.
//
ICMPv6OutStats.icmps_errors++;
(*Callback)(ControlBlock, Status, &UnspecifiedAddr, 0, NULL, 0);
goto common_echo_cleanup;
} // ICMPv6EchoRequest
//* ICMPv6EchoComplete - Common completion routine for echo requests.
//
// This is the routine is called by the OS-specific code to process an
// ICMP echo response.
//
NTSTATUS
ICMPv6EchoComplete(
EchoControl *ControlBlock, // ControlBlock of completed request.
IP_STATUS Status, // Status of the reply.
const IPv6Addr *Address, // Source of the reply.
uint ScopeId, // Scope of the reply.
void *Data, // Reply data (may be NULL).
uint DataSize, // Amount of reply data.
ULONG_PTR *BytesReturned) // Total user bytes returned.
{
PICMPV6_ECHO_REPLY ReplyBuffer;
LARGE_INTEGER Now, Freq;
//
// Sanity check our reply buffer length.
//
if (ControlBlock->ReplyBufLen < sizeof *ReplyBuffer) {
*BytesReturned = 0;
return STATUS_BUFFER_TOO_SMALL;
}
ReplyBuffer = (PICMPV6_ECHO_REPLY) ControlBlock->ReplyBuf;
//
// Fill in fields to return.
//
ReplyBuffer->Address.sin6_port = 0;
ReplyBuffer->Address.sin6_flowinfo = 0;
RtlCopyMemory(ReplyBuffer->Address.sin6_addr, Address, sizeof *Address);
ReplyBuffer->Address.sin6_scope_id = ScopeId;
ReplyBuffer->Status = Status;
//
// Return the elapsed time in milliseconds.
//
Now = KeQueryPerformanceCounter(&Freq);
ReplyBuffer->RoundTripTime = (uint)
((1000 * (Now.QuadPart - ControlBlock->WhenIssued.QuadPart)) /
Freq.QuadPart);
//
// Verify we have enough space in the reply buffer for the reply data.
//
if (ControlBlock->ReplyBufLen < sizeof *ReplyBuffer + DataSize) {
*BytesReturned = sizeof *ReplyBuffer;
return STATUS_BUFFER_TOO_SMALL;
}
//
// Copy the reply data to follow the reply buffer.
//
RtlCopyMemory(ReplyBuffer + 1, Data, DataSize);
*BytesReturned = sizeof *ReplyBuffer + DataSize;
return STATUS_SUCCESS;
} // ICMPv6EchoComplete
//* ICMPv6EchoTimeout - expire aging unanswered echo requests.
//
// IPv6Timeout calls this routine whenever it thinks we might have
// echo requests outstanding.
//
// Callable from DPC context, not from thread context.
// Called with no locks held.
//
void
ICMPv6EchoTimeout(void)
{
EchoControl *This, **PrevPtr, *TimedOut;
TimedOut = NULL;
//
// Grab the outstanding echo list lock and run through the list looking
// for requests that have timed out.
//
KeAcquireSpinLockAtDpcLevel(&ICMPv6EchoLock);
PrevPtr = &ICMPv6OutstandingEchos;
while ((This = *PrevPtr) != NULL) {
if (This->TimeoutTimer != 0) {
//
// Timer is running. Decrement and check for expiration.
//
if (--This->TimeoutTimer == 0) {
//
// This echo request has been sent and timed out without
// being answered. Move it to our timed out list.
//
*PrevPtr = This->Next;
This->Next = TimedOut;
TimedOut = This;
} else {
PrevPtr = &This->Next;
}
}
}
KeReleaseSpinLockFromDpcLevel(&ICMPv6EchoLock);
//
// Run through the list of timed out echoes, calling the completion
// routine on each. The completion routine is responsible for
// freeing the EchoControl block structure.
//
while (TimedOut != NULL) {
This = TimedOut;
TimedOut = This->Next;
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ICMPv6EchoTimeout: seq number 0x%x timed out\n", This->Seq));
(*This->CompleteRoutine)(This, IP_REQ_TIMED_OUT,
&UnspecifiedAddr, 0, NULL, 0);
}
}