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
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);
|
|
}
|
|
}
|