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