// -*- 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); for (This = ICMPv6OutstandingEchos, PrevPtr = &ICMPv6OutstandingEchos; This != NULL; This = This->Next) { 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; break; } 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); for (This = ICMPv6OutstandingEchos, PrevPtr = &ICMPv6OutstandingEchos; This != NULL; This = This->Next) { 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); 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; } 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); for (This = ICMPv6OutstandingEchos, PrevPtr = &ICMPv6OutstandingEchos; This != (EchoControl *)NULL; This = This->Next) { 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); } }