windows-nt/Source/XPSP1/NT/net/tcpip/tpipv6/tcpip6/ip6/receive.c

3242 lines
108 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
// -*- 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:
//
// Receive routines for Internet Protocol Version 6.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "icmp.h"
#include "route.h"
#include "fragment.h"
#include "mobile.h"
#include "security.h"
#include "info.h"
#include "ipsec.h"
struct ReassemblyList ReassemblyList;
typedef struct Options {
uint JumboLength; // Length of packet excluding IPv6 header.
IPv6RouterAlertOption UNALIGNED *Alert;
IPv6HomeAddressOption UNALIGNED *HomeAddress;
IPv6BindingUpdateOption UNALIGNED *BindingUpdate;
} Options;
int
ParseOptions(
IPv6Packet *Packet, // The packet handed to us by IPv6Receive.
uchar HdrType, // Hop-by-hop or destination.
IPv6OptionsHeader *Hdr, // Header with following data.
uint HdrLength, // Length of the entire options area.
Options *Opts); // Return option values to caller.
extern void TCPRcvComplete(void);
//* IPv6ReceiveComplete - Handle a receive complete.
//
// Called by the lower layer when receives are temporarily done.
//
void
IPv6ReceiveComplete(void)
{
// REVIEW: Original IP implementation had code here to call every
// REVIEW: UL protocol's receive complete routine (yes, all of them) here.
TCPRcvComplete();
}
//
// By default, test pullup in checked builds.
//
#ifndef PULLUP_TEST
#define PULLUP_TEST DBG
#endif
#if PULLUP_TEST
#define PULLUP_TEST_MAX_BUFFERS 8
#define PULLUP_TEST_MAX_BUFFER_SIZE 32
//* PullupTestChooseDistribution
//
// Choose a random distribution.
// Divides Size bytes into NumBuffers pieces,
// and returns the result in the Counts array.
//
void
PullupTestChooseDistribution(
uint Counts[],
uint NumBuffers,
uint Size)
{
uint i;
uint ThisBuffer;
//
// We are somewhat biased towards cutting the packet
// up into small pieces with a large remainder.
// This puts the fragment boundaries at the beginning,
// where the headers are.
//
for (i = 0; i < NumBuffers - 1; i++) {
ThisBuffer = RandomNumber(1, PULLUP_TEST_MAX_BUFFER_SIZE);
//
// Make sure that each segment has non-zero length.
//
if (ThisBuffer > Size - (NumBuffers - 1 - i))
ThisBuffer = Size - (NumBuffers - 1 - i);
Counts[i] = ThisBuffer;
Size -= ThisBuffer;
}
Counts[i] = Size;
}
//* PullupTestCreatePacket
//
// Given an IPv6 packet, creates a new IPv6 packet
// that can be handed up the receive path.
//
// We randomly fragment the IPv6 packet into multiple buffers.
// This tests pull-up processing in the receive path.
//
// Returns NULL if any memory allocation fails.
//
IPv6Packet *
PullupTestCreatePacket(IPv6Packet *Packet)
{
IPv6Packet *TestPacket;
//
// We mostly want to test discontiguous packets.
// But occasionally test a contiguous packet.
//
if (RandomNumber(0, 10) == 0) {
//
// We need to create a contiguous packet.
//
uint Padding;
uint MemLen;
void *Mem;
//
// We insert some padding to vary the alignment.
//
Padding = RandomNumber(0, 16);
MemLen = sizeof *TestPacket + Padding + Packet->TotalSize;
TestPacket = ExAllocatePool(NonPagedPool, MemLen);
if (TestPacket == NULL)
return NULL;
Mem = (void *)((uchar *)(TestPacket + 1) + Padding);
if (Packet->NdisPacket == NULL) {
RtlCopyMemory(Mem, Packet->Data, Packet->TotalSize);
}
else {
PNDIS_BUFFER NdisBuffer;
uint Offset;
int Ok;
NdisBuffer = NdisFirstBuffer(Packet->NdisPacket);
Offset = Packet->Position;
Ok = CopyNdisToFlat(Mem, NdisBuffer, Offset, Packet->TotalSize,
&NdisBuffer, &Offset);
ASSERT(Ok);
}
RtlZeroMemory(TestPacket, sizeof *TestPacket);
TestPacket->Data = TestPacket->FlatData = Mem;
TestPacket->ContigSize = TestPacket->TotalSize = Packet->TotalSize;
TestPacket->NTEorIF = Packet->NTEorIF;
TestPacket->Flags = Packet->Flags;
}
else {
//
// Create a packet with multiple NDIS buffers.
// Start with an over-estimate of the size of the MDLs we need.
//
uint NumPages = (Packet->TotalSize >> PAGE_SHIFT) + 2;
uint MdlRawSize = sizeof(MDL) + (NumPages * sizeof(PFN_NUMBER));
uint MdlAlign = __builtin_alignof(MDL) - 1;
uint MdlSize = (MdlRawSize + MdlAlign) &~ MdlAlign;
uint Padding;
uint MemLen;
uint Counts[PULLUP_TEST_MAX_BUFFERS];
uint NumBuffers;
void *Mem;
PNDIS_PACKET NdisPacket;
PNDIS_BUFFER NdisBuffer;
uint Garbage = 0xdeadbeef;
uint i;
//
// Choose the number of buffers/MDLs that we will use
// and the distribution of bytes into those buffers.
//
NumBuffers = RandomNumber(1, PULLUP_TEST_MAX_BUFFERS);
PullupTestChooseDistribution(Counts, NumBuffers, Packet->TotalSize);
//
// Allocate all the memory that we will need.
// (Actually a bit of an over-estimate.)
// We insert some padding to vary the initial alignment.
//
Padding = RandomNumber(0, 16);
MemLen = (sizeof *TestPacket + sizeof(NDIS_PACKET) +
NumBuffers * (MdlSize + sizeof Garbage) +
Padding + Packet->TotalSize);
TestPacket = ExAllocatePool(NonPagedPool, MemLen);
if (TestPacket == NULL)
return NULL;
NdisPacket = (PNDIS_PACKET)(TestPacket + 1);
NdisBuffer = (PNDIS_BUFFER)(NdisPacket + 1);
Mem = (void *)((uchar *)NdisBuffer + NumBuffers * MdlSize + Padding);
//
// Initialize the NDIS packet and buffers.
//
RtlZeroMemory(NdisPacket, sizeof *NdisPacket);
for (i = 0; i < NumBuffers; i++) {
MmInitializeMdl(NdisBuffer, Mem, Counts[i]);
MmBuildMdlForNonPagedPool(NdisBuffer);
NdisChainBufferAtBack(NdisPacket, NdisBuffer);
RtlCopyMemory((uchar *)Mem + Counts[i], &Garbage, sizeof Garbage);
(uchar *)Mem += Counts[i] + sizeof Garbage;
(uchar *)NdisBuffer += MdlSize;
}
//
// Copy data to the new packet.
//
CopyToBufferChain((PNDIS_BUFFER)(NdisPacket + 1), 0,
Packet->NdisPacket, Packet->Position,
Packet->FlatData, Packet->TotalSize);
//
// Initialize the new packet.
//
InitializePacketFromNdis(TestPacket, NdisPacket, 0);
TestPacket->NTEorIF = Packet->NTEorIF;
TestPacket->Flags = Packet->Flags;
}
return TestPacket;
}
#endif // PULLUP_TEST
//* IPv6Receive - Receive an incoming IPv6 datagram.
//
// This is the routine called by the link layer module when an incoming IPv6
// datagram is to be processed. We validate the datagram and decide what to
// do with it.
//
// The Packet->NTEorIF field holds the NTE or interface that is receiving
// the packet. Typically this is an interface, but there are some tunnel
// situations where the link layer has already found an NTE.
//
// Either the caller should hold a reference to the NTE or interface
// across the call, or the caller can place a reference in the Packet
// with PACKET_HOLDS_REF. If the caller specifies PACKET_HOLDS_REF,
/// IPv6Receive will release the reference.
//
// There is one exception: the caller can supply an interface
// with zero references (not using PACKET_HOLDS_REF),
// if the interface is being destroyed but IF->Cleanup has not yet returned.
//
// NB: The datagram may either be held in a NDIS_PACKET allocated by the
// link-layer or the interface driver (in which case 'Packet->NdisPacket'
// is non-NULL and 'Data' points to the first data buffer in the buffer
// chain), or the datagram may still be held by NDIS (in which case
// 'Packet->NdisPacket' is NULL and 'Data' points to a buffer containing
// the entire datagram).
//
// NB: We do NOT check for link-level multi/broadcasts to
// IPv6 unicast destinations. In the IPv4 world, receivers dropped
// such packets, but in the IPv6 world they are accepted.
//
// Returns count of references for the packet.
// For now, this should always be zero.
// Someday in the future this might be used to indicate
// that the IPv6 layer has not finished its receive processing.
//
// Callable from DPC context, not from thread context.
//
int
IPv6Receive(IPv6Packet *Packet)
{
uchar NextHeader; // Current header's NextHeader field.
uchar (*Handler)();
SALinkage *ThisSA, *NextSA;
int PktRefs;
ASSERT((Packet->FlatData == NULL) != (Packet->NdisPacket == NULL));
ASSERT(Packet->NTEorIF != NULL);
ASSERT(Packet->SAPerformed == NULL);
IPSIncrementInReceiveCount();
//
// Ensure that the packet is accessible in the kernel address space.
// If any mappings fail, just drop the packet.
// In practice, the packet buffers are usually already mapped.
// But they may not be, for example in loopback.
//
if (Packet->NdisPacket != NULL) {
NDIS_BUFFER *Buffer;
Buffer = NdisFirstBuffer(Packet->NdisPacket);
if (! MapNdisBuffers(Buffer)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"IPv6Receive(%p): buffer mapping failed\n",
Packet));
IPSInfo.ipsi_indiscards++;
return 0; // Drop the packet.
}
}
#if PULLUP_TEST
Packet = PullupTestCreatePacket(Packet);
if (Packet == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"IPv6Receive(%p): PullupTestCreatePacket failed\n",
Packet));
IPSInfo.ipsi_indiscards++;
return 0; // Drop the packet.
}
#endif
//
// Iteratively switch out to the handler for each successive next header
// until we reach a handler that reports no more headers follow it.
//
// NB: We do NOT check NTE->DADStatus here.
// That is the responsibility of higher-level protocols.
//
NextHeader = IP_PROTOCOL_V6; // Always first header in packet.
do {
//
// Current header indicates that another header follows.
// See if we have a handler for it.
//
Handler = ProtocolSwitchTable[NextHeader].DataReceive;
if (Handler == NULL) {
//
// We don't have a handler for this header type,
// so see if there is a raw receiver for it.
//
if (!RawReceive(Packet, NextHeader)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6 Receive: Next Header type %u not handled.\n",
NextHeader));
//
// There isn't a raw receiver either.
// Send an ICMP error message.
// ICMP Pointer value is the offset from the start of the
// incoming packet's IPv6 header to the offending field.
//
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_UNRECOGNIZED_NEXT_HEADER,
Packet->NextHeaderPosition -
Packet->IPPosition,
NextHeader, FALSE);
IPSInfo.ipsi_inunknownprotos++;
} else {
IPSIncrementInDeliverCount();
}
break; // We can't do anything more with this packet.
}
NextHeader = (*Handler)(Packet);
} while (NextHeader != IP_PROTOCOL_NONE);
//
// If this packet holds a reference, free it now.
//
if (Packet->Flags & PACKET_HOLDS_REF) {
if (IsNTE(Packet->NTEorIF))
ReleaseNTE(CastToNTE(Packet->NTEorIF));
else
ReleaseIF(CastToIF(Packet->NTEorIF));
}
//
// Clean up any contiguous regions left by PacketPullup.
//
PacketPullupCleanup(Packet);
//
// Clean up list of SA's performed.
//
for (ThisSA = Packet->SAPerformed; ThisSA != NULL; ThisSA = NextSA) {
ReleaseSA(ThisSA->This);
NextSA = ThisSA->Next;
ExFreePool(ThisSA);
}
PktRefs = Packet->RefCnt;
#if PULLUP_TEST
ExFreePool(Packet);
#endif
return PktRefs;
}
//* IPv6HeaderReceive - Handle a IPv6 header.
//
// This is the routine called to process an IPv6 header, a next header
// value of 41 (e.g. as would be encountered with v6 in v6 tunnels). To
// avoid code duplication, it is also used to process the initial IPv6
// header found in all IPv6 packets, in which mode it may be viewed as
// a continuation of IPv6Receive.
//
uchar
IPv6HeaderReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
uint PayloadLength;
uchar NextHeader;
int Forwarding; // TRUE means Forwarding, FALSE means Receiving.
//
// Sanity-check ContigSize & TotalSize.
// Higher-level code in the receive path relies on these conditions.
//
ASSERT(Packet->ContigSize <= Packet->TotalSize);
//
// If we are decapsulating a packet,
// remember that this packet was originally tunneled.
//
// Some argue that decapsulating and receiving
// the inner packet on the same interface as the outer packet
// is incorrect: the inner packet should be received
// on a tunnel interface distinct from the original interface.
// (This approach introduces some issues with handling
// IPsec encapsulation, especially tunnel-mode IPsec between peers
// where you want the inner & outer source address to be the same.)
//
// In any case, for now we receive the inner packet on the original
// interface. However, this introduces a potential security
// problem. An off-link node can send an encapsulated packet
// that when decapsulated, appears to have originated from
// an on-link neighbor. This is a security problem for ND.
// We can not conveniently decrement the HopLimit (to make ND's
// check against 255 effective in this case), because the packet
// is read-only. Instead, we remember that the packet is tunneled
// and check this flag bit in the ND code.
//
if (Packet->IP != NULL) {
Packet->Flags |= PACKET_TUNNELED;
Packet->Flags &= ~PACKET_SAW_HA_OPT; // Forget if we saw one.
Packet->SkippedHeaderLength = 0;
//
// If we've already done some IPSec processing on this packet,
// then this is a tunnel header and the preceeding IPSec header
// is operating in tunnel mode.
//
if (Packet->SAPerformed != NULL)
Packet->SAPerformed->Mode = TUNNEL;
} else {
//
// In the reassembly path, we remember if the fragments were
// tunneled but we do not have a Packet->IP.
//
ASSERT((((Packet->Flags & PACKET_TUNNELED) == 0) ||
(Packet->Flags & PACKET_REASSEMBLED)) &&
((Packet->Flags & PACKET_SAW_HA_OPT) == 0) &&
(Packet->SAPerformed == NULL));
}
//
// Make sure we have enough contiguous bytes for an IPv6 header, otherwise
// attempt to pullup that amount. Then stash away a pointer to the header
// and also remember the offset into the packet at which it begins (needed
// to calculate an offset for certain ICMP error messages).
//
if (! PacketPullup(Packet, sizeof(IPv6Header),
__builtin_alignof(IPv6Addr), 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof(IPv6Header))
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6HeaderReceive: "
"Packet too small to contain IPv6 header\n"));
IPSInfo.ipsi_inhdrerrors++;
return IP_PROTOCOL_NONE;
}
Packet->IP = (IPv6Header UNALIGNED *)Packet->Data;
Packet->IPPosition = Packet->Position;
Packet->NextHeaderPosition = Packet->Position +
FIELD_OFFSET(IPv6Header, NextHeader);
//
// Skip over IPv6 header (note we keep our pointer to it).
//
AdjustPacketParams(Packet, sizeof(IPv6Header));
//
// Check the IP version is correct.
// We specifically do NOT check HopLimit.
// HopLimit is only checked when forwarding.
//
if ((Packet->IP->VersClassFlow & IP_VER_MASK) != IP_VERSION) {
// Silently discard the packet.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6HeaderReceive: bad version\n"));
IPSInfo.ipsi_inhdrerrors++;
return IP_PROTOCOL_NONE;
}
//
// We use a separate pointer to refer to the source address so that
// later options can change it.
//
Packet->SrcAddr = AlignAddr(&Packet->IP->Source);
//
// Protect against attacks that use bogus source addresses.
//
if (IsInvalidSourceAddress(Packet->SrcAddr)) {
// Silently discard the packet.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6HeaderReceive: source address is invalid\n"));
return IP_PROTOCOL_NONE;
}
if (IsLoopback(Packet->SrcAddr) &&
((Packet->Flags & PACKET_LOOPED_BACK) == 0)) {
// Silently discard the packet.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6HeaderReceive: loopback source addr from wire?\n"));
return IP_PROTOCOL_NONE;
}
if (IsNTE(Packet->NTEorIF)) {
NetTableEntry *NTE;
//
// We were called with an NTE.
// Our caller (or the packet itself) should be holding a reference.
// The NTE holds an interface reference.
//
NTE = CastToNTE(Packet->NTEorIF);
//
// Verify that the packet's destination address is
// consistent with this NTE.
//
if (!IP6_ADDR_EQUAL(AlignAddr(&Packet->IP->Dest), &NTE->Address)) {
Interface *IF = NTE->IF;
//
// We can't accept this new header on this NTE.
// Convert to an Interface and punt to forwarding code below.
//
if (Packet->Flags & PACKET_HOLDS_REF) {
AddRefIF(IF);
ReleaseNTE(NTE);
}
else {
//
// Our caller holds a reference for the NTE,
// which holds a reference for the interface.
// So the packet does not need to hold a reference.
//
}
Packet->NTEorIF = CastFromIF(IF);
goto Forward;
}
//
// We are Receiving the packet.
//
Forwarding = FALSE;
} else {
NetTableEntryOrInterface *NTEorIF;
ushort Type;
//
// We were called with an Interface.
// In some situations, there is no reference for this interface
// and the interface is being destroyed. FindAddressOnInterface
// will return NULL in that case. After this point, we must ensure
// that the interface does have a reference, by having the packet
// hold a reference for the interface or a reference for an NTE
// on the interface.
//
NTEorIF = FindAddressOnInterface(CastToIF(Packet->NTEorIF),
AlignAddr(&Packet->IP->Dest), &Type);
if (NTEorIF == NULL) {
//
// The interface is being destroyed.
//
IPSInfo.ipsi_indiscards++;
return IP_PROTOCOL_NONE;
}
//
// FindAddressOnInterface returned a reference to NTEorIF
// (which could be an interface or an NTE). We either need
// to put this reference into the packet, or release it
// if the packet already holds an appropriate reference.
//
if (Type == ADE_NONE) {
//
// If the packet does not hold a reference for the interface,
// give it one now.
//
ASSERT(NTEorIF == Packet->NTEorIF);
if (Packet->Flags & PACKET_HOLDS_REF) {
//
// The packet already holds an interface reference,
// so our reference is not neeeded.
//
ReleaseIF(CastToIF(NTEorIF));
}
else {
//
// Give the packet our interface reference.
//
Packet->Flags |= PACKET_HOLDS_REF;
}
//
// The address is not assigned to this interface. Check to see
// if it is appropriate for us to forward this packet.
// If not, drop it. At this point, we are fairly
// conservative about what we will forward.
//
Forward:
if (!(CastToIF(Packet->NTEorIF)->Flags & IF_FLAG_FORWARDS) ||
(Packet->Flags & PACKET_NOT_LINK_UNICAST) ||
IsUnspecified(AlignAddr(&Packet->IP->Source)) ||
IsLoopback(AlignAddr(&Packet->IP->Source))) {
//
// Drop the packet with no ICMP error.
//
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
}
//
// No support yet for forwarding multicast packets.
//
if (IsUnspecified(AlignAddr(&Packet->IP->Dest)) ||
IsLoopback(AlignAddr(&Packet->IP->Dest)) ||
IsMulticast(AlignAddr(&Packet->IP->Dest))) {
//
// Send an ICMP error.
//
ICMPv6SendError(Packet,
ICMPv6_DESTINATION_UNREACHABLE,
ICMPv6_COMMUNICATION_PROHIBITED,
0, Packet->IP->NextHeader, FALSE);
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
}
//
// We do the actual forwarding below...
//
Forwarding = TRUE;
} else {
//
// If we found a unicast ADE, then remember the NTE.
// Conceptually, we think of the packet as holding
// the reference to the NTE. Normally for multicast/anycast
// addresses, we delay our choice of an appropriate NTE
// until it is time to reply to the packet.
//
if (IsNTE(NTEorIF)) {
NetTableEntry *NTE = CastToNTE(NTEorIF);
Interface *IF = NTE->IF;
ASSERT(CastFromIF(IF) == Packet->NTEorIF);
if (!IsValidNTE(NTE)) {
//
// The unicast address is not valid, so it can't
// receive packets. The address may be assigned
// to some other node, so forwarding is appropriate.
//
// Ensure that the packet holds an interface reference.
//
if (!(Packet->Flags & PACKET_HOLDS_REF)) {
//
// The packet does not already hold an interface ref,
// so give it one.
//
AddRefIF(IF);
Packet->Flags |= PACKET_HOLDS_REF;
}
//
// Now our NTE reference is not needed.
//
ReleaseNTE(NTE);
goto Forward;
}
//
// Ensure that the packet holds a reference for the NTE,
// which holds an interface reference.
//
if (Packet->Flags & PACKET_HOLDS_REF) {
//
// The packet already holds an interface reference.
// Release that reference and give the packet
// our NTE reference.
//
ReleaseIF(IF);
}
else {
//
// The packet does not hold a reference.
// Give the packet our NTE reference.
//
Packet->Flags |= PACKET_HOLDS_REF;
}
Packet->NTEorIF = CastFromNTE(NTE);
}
else {
//
// Ensure that the packet holds an interface reference.
//
ASSERT(NTEorIF == Packet->NTEorIF);
if (Packet->Flags & PACKET_HOLDS_REF) {
//
// The packet already holds an interface reference,
// so our reference is not needed.
//
ReleaseIF(CastToIF(NTEorIF));
}
else {
//
// Give our interface reference to the packet.
//
Packet->Flags |= PACKET_HOLDS_REF;
}
}
//
// We found an ADE on this IF to accept the packet,
// so we will be Receiving it.
//
Forwarding = FALSE;
}
}
//
// At this point, the Forwarding variable tells us
// if we are forwarding or receiving the packet.
//
//
// Before processing any headers, including Hop-by-Hop,
// check that the amount of payload the IPv6 header thinks is present
// can actually fit inside the packet data area that the link handed us.
// Note that a Payload Length of zero *might* mean a Jumbo Payload option.
//
PayloadLength = net_short(Packet->IP->PayloadLength);
if (PayloadLength > Packet->TotalSize) {
// Silently discard the packet.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"IPv6HeaderReceive: Header's PayloadLength is greater than "
"the amount of data received\n"));
IPSInfo.ipsi_inhdrerrors++;
return IP_PROTOCOL_NONE;
}
//
// Check for Hop-by-Hop Options.
//
if (Packet->IP->NextHeader == IP_PROTOCOL_HOP_BY_HOP) {
int RetVal;
//
// If there is a Jumbo Payload option, HopByHopOptionsReceive
// will adjust the packet size. Otherwise we take care of it
// now, before reading the Hop-by-Hop header.
//
if (PayloadLength != 0) {
Packet->TotalSize = PayloadLength;
if (Packet->ContigSize > PayloadLength)
Packet->ContigSize = PayloadLength;
}
//
// Parse the Hop-by-Hop options.
//
RetVal = HopByHopOptionsReceive(Packet);
if (RetVal < 0) {
//
// The packet had bad Hop-by-Hop Options.
// Drop it.
//
IPSInfo.ipsi_inhdrerrors++;
return IP_PROTOCOL_NONE;
}
NextHeader = (uchar)RetVal; // Truncate to 8 bits.
} else {
//
// No Jumbo Payload option. Adjust the packet size.
//
Packet->TotalSize = PayloadLength;
if (Packet->ContigSize > PayloadLength)
Packet->ContigSize = PayloadLength;
//
// No Hop-by-Hop options.
//
NextHeader = Packet->IP->NextHeader;
}
//
// Check if we are forwarding this packet.
//
if (Forwarding) {
IPv6Header UNALIGNED *FwdIP;
NDIS_PACKET *FwdPacket;
NDIS_STATUS NdisStatus;
uint Offset;
uint MemLen;
uchar *Mem;
uint TunnelStart = NO_TUNNEL, IPSecBytes = 0;
IPSecProc *IPSecToDo;
uint Action;
RouteCacheEntry *RCE;
IP_STATUS Status;
//
// Verify IPSec was performed.
//
if (InboundSecurityCheck(Packet, 0, 0, 0,
CastToIF(Packet->NTEorIF)) != TRUE) {
//
// No policy was found or the policy indicated to drop the packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"IPv6Receive: "
"IPSec lookup failed or policy was to drop\n"));
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
}
//
// At this time, we need to copy the incoming packet,
// for several reasons: We can't hold the Packet
// once IPv6HeaderReceive returns, yet we need to queue
// packet to forward it. We need to modify the packet
// (in IPv6Forward) by decrementing the hop count,
// yet our incoming packet is read-only. Finally,
// we need space in the outgoing packet for the outgoing
// interface's link-level header, which may differ in size
// from that of the incoming interface. Someday, we can
// implement support for returning a non-zero reference
// count from IPv6Receive and only copy the incoming
// packet's header to construct the outgoing packet.
//
//
// Find a route to the new destination.
//
Status = RouteToDestination(AlignAddr(&Packet->IP->Dest),
0, Packet->NTEorIF,
RTD_FLAG_LOOSE, &RCE);
if (Status != IP_SUCCESS) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"IPv6HeaderReceive: "
"No route to destination for forwarding.\n"));
ICMPv6SendError(Packet,
ICMPv6_DESTINATION_UNREACHABLE,
ICMPv6_NO_ROUTE_TO_DESTINATION,
0, NextHeader, FALSE);
IPSInfo.ipsi_outnoroutes++;
return IP_PROTOCOL_NONE;
}
//
// Find the Security Policy for this outbound traffic.
//
IPSecToDo = OutboundSPLookup(AlignAddr(&Packet->IP->Source),
AlignAddr(&Packet->IP->Dest),
0, 0, 0, RCE->NCE->IF, &Action);
if (IPSecToDo == NULL) {
//
// Check Action.
//
if (Action == LOOKUP_DROP) {
// Drop packet.
ReleaseRCE(RCE);
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
} else {
if (Action == LOOKUP_IKE_NEG) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"IPv6HeaderReceive: IKE not supported yet.\n"));
ReleaseRCE(RCE);
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
}
}
//
// With no IPSec to perform, IPv6Forward won't be changing the
// outgoing interface from what we currently think it will be.
// So we can use the exact size of its link-level header.
//
Offset = RCE->NCE->IF->LinkHeaderSize;
} else {
//
// Calculate the space needed for the IPSec headers.
//
IPSecBytes = IPSecBytesToInsert(IPSecToDo, &TunnelStart, NULL);
if (TunnelStart != 0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"IPv6HeaderReceive: IPSec Tunnel mode only.\n"));
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
ReleaseRCE(RCE);
IPSInfo.ipsi_inaddrerrors++;
return IP_PROTOCOL_NONE;
}
//
// The IPSec code in IPv6Forward might change the outgoing
// interface from what we currently think it will be.
// Leave the max amount of space for its link-level header.
//
Offset = MAX_LINK_HEADER_SIZE;
}
PayloadLength = Packet->TotalSize;
MemLen = Offset + sizeof(IPv6Header) + PayloadLength + IPSecBytes;
NdisStatus = IPv6AllocatePacket(MemLen, &FwdPacket, &Mem);
if (NdisStatus != NDIS_STATUS_SUCCESS) {
if (IPSecToDo) {
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
}
ReleaseRCE(RCE);
IPSInfo.ipsi_indiscards++;
return IP_PROTOCOL_NONE; // We can't forward.
}
FwdIP = (IPv6Header UNALIGNED *)(Mem + Offset + IPSecBytes);
//
// Copy from the incoming packet to the outgoing packet.
//
CopyPacketToBuffer((uchar *)FwdIP, Packet,
sizeof(IPv6Header) + PayloadLength,
Packet->IPPosition);
//
// Send the outgoing packet.
//
IPv6Forward(Packet->NTEorIF, FwdPacket, Offset + IPSecBytes, FwdIP,
PayloadLength, TRUE, // OK to Redirect.
IPSecToDo, RCE);
if (IPSecToDo) {
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
}
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE;
} // end of if (Forwarding)
//
// Packet is for this node.
// Note: We may only be an intermediate node and not the packet's final
// destination, if there is a routing header.
//
return NextHeader;
}
//* ReassemblyInit
//
// Initialize data structures required for fragment reassembly.
//
void
ReassemblyInit(void)
{
KeInitializeSpinLock(&ReassemblyList.Lock);
ReassemblyList.First = ReassemblyList.Last = SentinelReassembly;
KeInitializeSpinLock(&ReassemblyList.LockSize);
}
//* ReassemblyUnload
//
// Cleanup the fragment reassembly data structures and
// prepare for stack unload.
//
void
ReassemblyUnload(void)
{
//
// We are called after all interfaces have been destroyed,
// so the reassemblies should already be gone.
//
ASSERT(ReassemblyList.Last == SentinelReassembly);
ASSERT(ReassemblyList.Size == 0);
}
//* ReassemblyRemove
//
// Cleanup the fragment reassembly data structures
// when an interface becomes invalid.
//
// Callable from DPC or thread context.
//
void
ReassemblyRemove(Interface *IF)
{
Reassembly *DeleteList = NULL;
Reassembly *Reass, *NextReass;
KIRQL OldIrql;
KeAcquireSpinLock(&ReassemblyList.Lock, &OldIrql);
for (Reass = ReassemblyList.First;
Reass != SentinelReassembly;
Reass = NextReass) {
NextReass = Reass->Next;
if (Reass->IF == IF) {
//
// Remove this reassembly.
// If it is not already being deleted,
// put it on our temporary list.
//
RemoveReassembly(Reass);
KeAcquireSpinLockAtDpcLevel(&Reass->Lock);
if (Reass->State == REASSEMBLY_STATE_DELETING) {
//
// Note that it has been removed from the list.
//
Reass->State = REASSEMBLY_STATE_REMOVED;
}
else {
Reass->Next = DeleteList;
DeleteList = Reass;
}
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
}
}
KeReleaseSpinLock(&ReassemblyList.Lock, OldIrql);
//
// Actually free the reassemblies that we removed above.
//
while ((Reass = DeleteList) != NULL) {
DeleteList = Reass->Next;
DeleteReassembly(Reass);
}
}
//* FragmentReceive - Handle a IPv6 datagram fragment.
//
// This is the routine called by IPv6 when it receives a fragment of an
// IPv6 datagram, i.e. a next header value of 44. Here we attempt to
// reassemble incoming fragments into complete IPv6 datagrams.
//
// If a later fragment provides data that conflicts with an earlier
// fragment, then we use the first-arriving data.
//
// We silently drop the fragment and stop reassembly in several
// cases that are not specified in the spec, to prevent DoS attacks.
// These include partially overlapping fragments and fragments
// that carry no data. Legitimate senders should never generate them.
//
uchar
FragmentReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
Interface *IF = Packet->NTEorIF->IF;
FragmentHeader UNALIGNED *Frag;
Reassembly *Reass;
ushort FragOffset;
PacketShim *Shim, *ThisShim, **MoveShim;
uint NextHeaderPosition;
IPSInfo.ipsi_reasmreqds++;
//
// We can not reassemble fragments that have had IPsec processing.
// It can't work because the IPsec headers in the unfragmentable part
// of the offset-zero fragment will authenticate/decrypt that fragment.
// Then the same headers would be copied to the reassembled packet.
// They couldn't possibly successfully authenticate/decrypt again.
// Also see RFC 2401 B.2.
//
if (Packet->SAPerformed != NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"FragmentReceive: IPsec on fragment\n"));
//
// The spec does not tell us what ICMP error to generate in this case,
// but flagging the fragment header seems reasonable.
//
goto BadFragment;
}
//
// If a jumbo payload option was seen, send an ICMP error.
// Set ICMP pointer to the offset of the fragment header.
//
if (Packet->Flags & PACKET_JUMBO_OPTION) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"FragmentReceive: jumbo fragment\n"));
BadFragment:
//
// The NextHeader value passed to ICMPv6SendError
// is IP_PROTOCOL_FRAGMENT because we haven't moved
// past the fragment header yet.
//
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
Packet->Position - Packet->IPPosition,
IP_PROTOCOL_FRAGMENT, FALSE);
goto Failed; // Drop packet.
}
//
// Verify that we have enough contiguous data to overlay a FragmentHeader
// structure on the incoming packet. Then do so.
//
if (! PacketPullup(Packet, sizeof *Frag, 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof *Frag)
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
IP_PROTOCOL_NONE, FALSE);
goto Failed; // Drop packet.
}
Frag = (FragmentHeader UNALIGNED *) Packet->Data;
//
// Remember offset to this header's NextHeader field.
// But don't overwrite offset to previous header's NextHeader just yet.
//
NextHeaderPosition = Packet->Position +
FIELD_OFFSET(FragmentHeader, NextHeader);
//
// Skip over fragment header.
//
AdjustPacketParams(Packet, sizeof *Frag);
//
// Lookup this fragment triple (Source Address, Destination
// Address, and Identification field) per-interface to see if
// we've already received other fragments of this packet.
//
Reass = FragmentLookup(IF, Frag->Id,
AlignAddr(&Packet->IP->Source),
AlignAddr(&Packet->IP->Dest));
if (Reass == NULL) {
//
// We hold the global reassembly list lock.
//
// Handle a special case first: if this is the first, last, and only
// fragment, then we can just continue parsing without reassembly.
// Test both paths in checked builds.
//
if ((Frag->OffsetFlag == 0)
#if DBG
&& ((int)Random() < 0)
#endif
) {
//
// Return next header value.
//
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
Packet->NextHeaderPosition = NextHeaderPosition;
Packet->SkippedHeaderLength += sizeof(FragmentHeader);
IPSInfo.ipsi_reasmoks++;
return Frag->NextHeader;
}
//
// We must avoid creating new reassembly records
// if the interface is going away, to prevent races
// with DestroyIF/ReassemblyRemove.
//
if (IsDisabledIF(IF)) {
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
goto Failed;
}
//
// This is the first fragment of this datagram we've received.
// Allocate a reassembly structure to keep track of the pieces.
//
Reass = ExAllocatePool(NonPagedPool, sizeof(struct Reassembly));
if (Reass == NULL) {
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"FragmentReceive: Couldn't allocate memory!?!\n"));
goto Failed;
}
KeInitializeSpinLock(&Reass->Lock);
Reass->State = REASSEMBLY_STATE_NORMAL;
RtlCopyMemory(&Reass->IPHdr, Packet->IP, sizeof(IPv6Header));
Reass->IF = IF;
Reass->Id = Frag->Id;
Reass->ContigList = NULL;
#if DBG
Reass->ContigEnd = NULL;
#endif
Reass->GapList = NULL;
Reass->Timer = DEFAULT_REASSEMBLY_TIMEOUT;
Reass->Marker = 0;
Reass->MaxGap = 0;
//
// We must initialize DataLength to an invalid value.
// Initializing to zero doesn't work.
//
Reass->DataLength = (uint)-1;
Reass->UnfragmentLength = 0;
Reass->UnfragData = NULL;
Reass->Flags = 0;
Reass->Size = REASSEMBLY_SIZE_PACKET;
//
// Add new Reassembly struct to front of the ReassemblyList.
// Acquires the reassembly record lock and
// releases the global reassembly list lock.
//
AddToReassemblyList(Reass);
}
else {
//
// We have found and locked an existing reassembly structure.
// Because we remove the reassembly structure in every
// error situation below, an existing reassembly structure
// must have a shim that has been successfully added to it.
//
ASSERT((Reass->ContigList != NULL) || (Reass->GapList != NULL));
}
//
// At this point, we have a locked reassembly record.
// We do not hold the global reassembly list lock
// while we perform the relatively expensive work
// of copying the fragment.
//
ASSERT(Reass->State == REASSEMBLY_STATE_NORMAL);
//
// Update the saved packet flags from this fragment packet.
// We are really only interested in PACKET_NOT_LINK_UNICAST.
//
Reass->Flags |= Packet->Flags;
FragOffset = net_short(Frag->OffsetFlag) & FRAGMENT_OFFSET_MASK;
//
// Send ICMP error if this fragment causes the total packet length
// to exceed 65,535 bytes. Set ICMP pointer equal to the offset to
// the Fragment Offset field.
//
if (FragOffset + Packet->TotalSize > MAX_IPv6_PAYLOAD) {
DeleteFromReassemblyList(Reass);
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(Packet->Position - sizeof(FragmentHeader) +
(uint)FIELD_OFFSET(FragmentHeader, OffsetFlag) -
Packet->IPPosition),
((FragOffset == 0) ?
Frag->NextHeader : IP_PROTOCOL_NONE),
FALSE);
goto Failed;
}
if ((Packet->TotalSize == 0) && (Frag->OffsetFlag != 0)) {
//
// We allow a moot fragment header (Frag->OffsetFlag == 0),
// because some test programs might generate them.
// (The first/last/only check above catches this in free builds.)
// But otherwise, we disallow fragments that do not actually
// carry any data for DoS protection.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"FragmentReceive: zero data fragment\n"));
DeleteFromReassemblyList(Reass);
return IP_PROTOCOL_NONE;
}
//
// If this is the last fragment (more fragments bit not set), then
// remember the total data length, else, check that the length
// is a multiple of 8 bytes.
//
if ((net_short(Frag->OffsetFlag) & FRAGMENT_FLAG_MASK) == 0) {
if (Reass->DataLength != (uint)-1) {
//
// We already received a last fragment.
// This can happen if a packet is duplicated.
//
if (FragOffset + Packet->TotalSize != Reass->DataLength) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"FragmentReceive: second last fragment\n"));
DeleteFromReassemblyList(Reass);
return IP_PROTOCOL_NONE;
}
}
else {
//
// Set expected data length from this fragment.
//
Reass->DataLength = FragOffset + Packet->TotalSize;
//
// Do we have any fragments beyond this length?
//
if ((Reass->Marker > Reass->DataLength) ||
(Reass->MaxGap > Reass->DataLength))
goto BadFragmentBeyondData;
}
} else {
if ((Packet->TotalSize % 8) != 0) {
//
// Length is not multiple of 8, send ICMP error with a pointer
// value equal to offset of payload length field in IP header.
//
DeleteFromReassemblyList(Reass);
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
((FragOffset == 0) ?
Frag->NextHeader : IP_PROTOCOL_NONE),
FALSE);
goto Failed; // Drop packet.
}
if ((Reass->DataLength != (uint)-1) &&
(FragOffset + Packet->TotalSize > Reass->DataLength)) {
//
// This fragment falls beyond the data length.
// As part of our DoS prevention, drop the reassembly.
//
BadFragmentBeyondData:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"FragmentReceive: fragment beyond data length\n"));
DeleteFromReassemblyList(Reass);
return IP_PROTOCOL_NONE;
}
}
//
// Allocate and initialize a shim structure to hold the fragment data.
//
Shim = ExAllocatePool(NonPagedPool, sizeof *Shim + Packet->TotalSize);
if (Shim == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"FragmentReceive: Couldn't allocate memory!?!\n"));
DeleteFromReassemblyList(Reass);
goto Failed;
}
IncreaseReassemblySize(Reass, REASSEMBLY_SIZE_FRAG + Packet->TotalSize);
Shim->Len = (ushort)Packet->TotalSize;
Shim->Offset = FragOffset;
Shim->Next = NULL;
//
// Determine where this fragment fits among the previous ones.
//
// There is no good reason for senders to ever generate overlapping
// fragments. However, packets may sometimes be duplicated in the network.
// If we receive a fragment that duplicates previously received fragments,
// then we just discard it. If we receive a fragment that only partially
// overlaps previously received fragments, then we assume a malicious
// sender and just drop the reassembly. This gives us better behavior
// under some kinds of DoS attacks, although the upper bound on reassembly
// buffers (see CheckReassemblyQuota) is the ultimate protection.
//
if (FragOffset == Reass->Marker) {
//
// This fragment extends the contiguous list.
//
if (Reass->ContigList == NULL) {
//
// We're first on the list.
// We use info from the (first) offset zero fragment to recreate
// the original datagram. Info in a second offset zero fragment
// is ignored.
//
ASSERT(FragOffset == 0);
ASSERT(Reass->UnfragData == NULL);
Reass->ContigList = Shim;
// Save the next header value.
Reass->NextHeader = Frag->NextHeader;
//
// Grab the unfragmentable data, i.e. the extension headers that
// preceded the fragment header.
//
Reass->UnfragmentLength = (ushort)
(Packet->Position - sizeof(FragmentHeader)) -
(Packet->IPPosition + sizeof(IPv6Header));
if (Reass->UnfragmentLength != 0) {
Reass->UnfragData = ExAllocatePool(NonPagedPool,
Reass->UnfragmentLength);
if (Reass->UnfragData == NULL) {
// Out of memory!?! Clean up and drop packet.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"FragmentReceive: "
"Couldn't allocate memory?\n"));
// Will also free Shim because of Reass->ContigList.
DeleteFromReassemblyList(Reass);
goto Failed;
}
IncreaseReassemblySize(Reass, Reass->UnfragmentLength);
CopyPacketToBuffer(Reass->UnfragData, Packet,
Reass->UnfragmentLength,
Packet->IPPosition + sizeof(IPv6Header));
Reass->NextHeaderOffset = Packet->NextHeaderPosition -
Packet->IPPosition;
} else
Reass->NextHeaderOffset = FIELD_OFFSET(IPv6Header, NextHeader);
//
// We need to have the IP header of the offset-zero fragment.
// (Every fragment normally will have the same IP header,
// except for PayloadLength, and unfragmentable headers,
// but they might not.) ReassembleDatagram and
// CreateFragmentPacket both need it.
//
// Of the 40 bytes in the header, the 32 bytes in the source
// and destination addresses are already correct.
// So we just copy the other 8 bytes now.
//
RtlCopyMemory(&Reass->IPHdr, Packet->IP, 8);
} else {
//
// Add us to the end of the list.
//
Reass->ContigEnd->Next = Shim;
}
Reass->ContigEnd = Shim;
//
// Increment our contiguous extent marker.
//
Reass->Marker += (ushort)Packet->TotalSize;
//
// Now peruse the non-contiguous list here to see if we already
// have the next fragment to extend the contiguous list, and if so,
// move it on over. Repeat until we can't.
//
MoveShim = &Reass->GapList;
while ((ThisShim = *MoveShim) != NULL) {
if (ThisShim->Offset == Reass->Marker) {
//
// This fragment now extends the contiguous list.
// Add it to the end of the list.
//
Reass->ContigEnd->Next = ThisShim;
Reass->ContigEnd = ThisShim;
Reass->Marker += ThisShim->Len;
//
// Remove it from non-contiguous list.
//
*MoveShim = ThisShim->Next;
ThisShim->Next = NULL;
}
else if (ThisShim->Offset > Reass->Marker) {
//
// This fragment lies beyond the contiguous list.
// Because the gap list is sorted, we can stop now.
//
break;
}
else {
//
// This fragment overlaps the contiguous list.
// For DoS prevention, drop the reassembly.
//
BadFragmentOverlap:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"FragmentReceive: overlapping fragment\n"));
DeleteFromReassemblyList(Reass);
return IP_PROTOCOL_NONE;
}
}
} else {
//
// Exile this fragment to the non-contiguous (gap) list.
// The gap list is sorted by Offset.
//
MoveShim = &Reass->GapList;
for (;;) {
ThisShim = *MoveShim;
if (ThisShim == NULL) {
//
// Insert Shim at the end of the gap list.
//
Reass->MaxGap = Shim->Offset + Shim->Len;
break;
}
if (Shim->Offset < ThisShim->Offset) {
//
// Check for partial overlap.
//
if (Shim->Offset + Shim->Len > ThisShim->Offset) {
ExFreePool(Shim);
goto BadFragmentOverlap;
}
//
// OK, insert Shim before ThisShim.
//
break;
}
else if (ThisShim->Offset < Shim->Offset) {
//
// Check for partial overlap.
//
if (ThisShim->Offset + ThisShim->Len > Shim->Offset) {
ExFreePool(Shim);
goto BadFragmentOverlap;
}
//
// OK, insert Shim somewhere after ThisShim.
// Keep looking for the right spot.
//
MoveShim = &ThisShim->Next;
}
else {
//
// If the new fragment duplicates the old,
// then just ignore the new fragment.
//
if (Shim->Len == ThisShim->Len) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"FragmentReceive: duplicate fragment\n"));
ExFreePool(Shim);
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
return IP_PROTOCOL_NONE;
}
else {
ExFreePool(Shim);
goto BadFragmentOverlap;
}
}
}
Shim->Next = *MoveShim;
*MoveShim = Shim;
}
//
// Now that we have added the shim to the reassembly record
// and passed various checks (particularly DoS checks),
// copy the actual fragment data to the shim.
//
CopyPacketToBuffer(PacketShimData(Shim), Packet,
Packet->TotalSize, Packet->Position);
if (Reass->Marker == Reass->DataLength) {
//
// We have received all the fragments.
// Because of the overlapping/data-length/zero-size sanity checks
// above, when this happens there should be no fragments
// left on the gap list. However, ReassembleDatagram does not
// rely on having an empty gap list.
//
ASSERT(Reass->GapList == NULL);
ReassembleDatagram(Packet, Reass);
}
else {
//
// Finally, check if we're too close to our limit for
// reassembly buffers. If so, drop this packet. Otherwise,
// wait for more fragments to arrive.
//
CheckReassemblyQuota(Reass);
}
return IP_PROTOCOL_NONE;
Failed:
IPSInfo.ipsi_reasmfails++;
return IP_PROTOCOL_NONE;
}
//* FragmentLookup - look for record of previous fragments from this datagram.
//
// A datagram on an interface is uniquely identified by its
// {source address, destination address, identification field} triple.
// This function checks our reassembly list for previously
// received fragments of a given datagram.
//
// If an existing reassembly record is found,
// it is returned locked.
//
// If there is no existing reassembly record, returns NULL
// and leaves the global reassembly list locked.
//
// Callable from DPC context, not from thread context.
//
Reassembly *
FragmentLookup(
Interface *IF, // Receiving interface.
ulong Id, // Fragment identification field to match.
const IPv6Addr *Src, // Source address to match.
const IPv6Addr *Dst) // Destination address to match.
{
Reassembly *Reass;
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.Lock);
for (Reass = ReassemblyList.First;; Reass = Reass->Next) {
if (Reass == SentinelReassembly) {
//
// Return with the global reassembly list lock still held.
//
return NULL;
}
if ((Reass->IF == IF) &&
(Reass->Id == Id) &&
IP6_ADDR_EQUAL(&Reass->IPHdr.Source, Src) &&
IP6_ADDR_EQUAL(&Reass->IPHdr.Dest, Dst)) {
//
// Is this reassembly record being deleted?
// If so, ignore it.
//
KeAcquireSpinLockAtDpcLevel(&Reass->Lock);
ASSERT((Reass->State == REASSEMBLY_STATE_NORMAL) ||
(Reass->State == REASSEMBLY_STATE_DELETING));
if (Reass->State == REASSEMBLY_STATE_DELETING) {
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
continue;
}
//
// Return with the reassembly record lock still held.
//
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
return Reass;
}
}
}
//* AddToReassemblyList
//
// Add the reassembly record to the list.
// It must NOT already be on the list.
//
// Called with the global reassembly list lock held.
// Returns with the reassembly record lock held.
//
// Callable from DPC context, not from thread context.
//
void
AddToReassemblyList(Reassembly *Reass)
{
Reassembly *AfterReass = SentinelReassembly;
Reass->Prev = AfterReass;
(Reass->Next = AfterReass->Next)->Prev = Reass;
AfterReass->Next = Reass;
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.LockSize);
ReassemblyList.Size += Reass->Size;
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.LockSize);
//
// We must acquire the reassembly record lock
// *before* releasing the global reassembly list lock,
// to prevent the reassembly from diappearing underneath us.
//
KeAcquireSpinLockAtDpcLevel(&Reass->Lock);
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
}
//* RemoveReassembly
//
// Remove a reassembly record from the list.
//
// Called with the global reassembly lock held.
// The reassembly record lock may be held.
//
void
RemoveReassembly(Reassembly *Reass)
{
Reass->Prev->Next = Reass->Next;
Reass->Next->Prev = Reass->Prev;
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.LockSize);
ReassemblyList.Size -= Reass->Size;
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.LockSize);
}
//* IncreaseReassemblySize
//
// Increase the size of the reassembly record.
// Called with the reassembly record lock held.
//
// Callable from DPC context, not from thread context.
//
void
IncreaseReassemblySize(Reassembly *Reass, uint Size)
{
Reass->Size += Size;
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.LockSize);
ReassemblyList.Size += Size;
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.LockSize);
}
//* DeleteReassembly
//
// Delete a reassembly record.
//
void
DeleteReassembly(Reassembly *Reass)
{
PacketShim *ThisShim, *PrevShim;
//
// Free ContigList if populated.
//
PrevShim = ThisShim = Reass->ContigList;
while (ThisShim != NULL) {
PrevShim = ThisShim;
ThisShim = ThisShim->Next;
ExFreePool(PrevShim);
}
//
// Free GapList if populated.
//
PrevShim = ThisShim = Reass->GapList;
while (ThisShim != NULL) {
PrevShim = ThisShim;
ThisShim = ThisShim->Next;
ExFreePool(PrevShim);
}
//
// Free unfragmentable data.
//
if (Reass->UnfragData != NULL)
ExFreePool(Reass->UnfragData);
ExFreePool(Reass);
}
//* DeleteFromReassemblyList
//
// Remove and delete the reassembly record.
// The reassembly record MUST be on the list.
//
// Callable from DPC context, not from thread context.
// Called with the reassembly record lock held,
// but not the global reassembly list lock.
//
void
DeleteFromReassemblyList(Reassembly *Reass)
{
//
// Mark the reassembly as being deleted.
// This will prevent someone else from freeing it.
//
ASSERT(Reass->State == REASSEMBLY_STATE_NORMAL);
Reass->State = REASSEMBLY_STATE_DELETING;
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.Lock);
KeAcquireSpinLockAtDpcLevel(&Reass->Lock);
ASSERT((Reass->State == REASSEMBLY_STATE_DELETING) ||
(Reass->State == REASSEMBLY_STATE_REMOVED));
//
// Remove the reassembly record from the list,
// if someone else hasn't already removed it.
//
if (Reass->State != REASSEMBLY_STATE_REMOVED)
RemoveReassembly(Reass);
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
//
// Delete the reassembly record.
//
DeleteReassembly(Reass);
}
//* CheckReassemblyQuota
//
// Delete reassembly record if necessary,
// to keep the reassembly buffering under quota.
//
// Callable from DPC context, not from thread context.
// Called with the reassembly record lock held,
// but not the global reassembly list lock.
//
void
CheckReassemblyQuota(Reassembly *Reass)
{
int Prune = FALSE;
uint Threshold = ReassemblyList.Limit / 2;
//
// Decide whether to drop the reassembly record based on a RED-like
// algorithm. If the total size is less than 50% of the max, never
// drop. If the total size is over the max, always drop. If between
// 50% and 100% full, drop based on a probability proportional to the
// amount over 50%. This is an O(1) algorithm which is proportionally
// biased against large packets, and against sources which send more
// packets. This should provide a decent level of protection against
// DoS attacks.
//
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.LockSize);
if ((ReassemblyList.Size > Threshold) &&
(RandomNumber(0, Threshold) < ReassemblyList.Size - Threshold))
Prune = TRUE;
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.LockSize);
if (Prune) {
//
// Delete this reassembly record.
// We do not send ICMP errors in this situation.
// The reassembly timer has not expired.
// This is more analogous to a router dropping packets
// when a queue gets full, and no ICMP error is sent
// in that situation.
//
#if DBG
char Buffer1[INET6_ADDRSTRLEN], Buffer2[INET6_ADDRSTRLEN];
FormatV6AddressWorker(Buffer1, &Reass->IPHdr.Source);
FormatV6AddressWorker(Buffer2, &Reass->IPHdr.Dest);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"CheckReassemblyQuota: Src %s Dst %s Id %x\n",
Buffer1, Buffer2, Reass->Id));
#endif
DeleteFromReassemblyList(Reass);
}
else
KeReleaseSpinLockFromDpcLevel(&Reass->Lock);
}
typedef struct ReassembledReceiveContext {
WORK_QUEUE_ITEM WQItem;
IPv6Packet Packet;
uchar Data[];
} ReassembledReceiveContext;
//* ReassembledReceive
//
// Receive a reassembled packet.
// This function is called from a kernel worker thread context.
// It prevents "reassembly recursion".
//
void
ReassembledReceive(PVOID Context)
{
ReassembledReceiveContext *rrc = (ReassembledReceiveContext *) Context;
KIRQL Irql;
int PktRefs;
//
// All receive processing normally happens at DPC level,
// so we must pretend to be a DPC, so we raise IRQL.
// (System worker threads typically run at PASSIVE_LEVEL).
//
KeRaiseIrql(DISPATCH_LEVEL, &Irql);
PktRefs = IPv6Receive(&rrc->Packet);
ASSERT(PktRefs == 0);
KeLowerIrql(Irql);
ExFreePool(rrc);
}
//* ReassembleDatagram - put all the fragments together.
//
// Called when we have all the fragments to complete a datagram.
// Patch them together and pass the packet up.
//
// We allocate a single contiguous buffer and copy the fragments
// into this buffer.
// REVIEW: Instead use ndis buffers to chain the fragments?
//
// Callable from DPC context, not from thread context.
// Called with the reassembly record lock held,
// but not the global reassembly list lock.
//
// Deletes the reassembly record.
//
void
ReassembleDatagram(
IPv6Packet *Packet, // The packet being currently received.
Reassembly *Reass) // Reassembly record for fragmented datagram.
{
uint DataLen;
uint TotalLength;
uint memptr = sizeof(IPv6Header);
PacketShim *ThisShim, *PrevShim;
ReassembledReceiveContext *rrc;
IPv6Packet *ReassPacket;
uchar *ReassBuffer;
uchar *pNextHeader;
DataLen = Reass->DataLength + Reass->UnfragmentLength;
ASSERT(DataLen <= MAX_IPv6_PAYLOAD);
TotalLength = sizeof(IPv6Header) + DataLen;
//
// Allocate memory for buffer and copy fragment data into it.
// At the same time we allocate space for context information
// and an IPv6 packet structure.
//
rrc = ExAllocatePool(NonPagedPool, sizeof *rrc + TotalLength);
if (rrc == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"ReassembleDatagram: Couldn't allocate memory!?!\n"));
DeleteFromReassemblyList(Reass);
IPSInfo.ipsi_reasmfails++;
return;
}
//
// We must take a reference on the interface before
// DeleteFromReassemblyList releases the record lock.
//
ReassPacket = &rrc->Packet;
ReassBuffer = rrc->Data;
//
// Generate the original IP hdr and copy it and any unfragmentable
// data into the new packet. Note we have to update the next header
// field in the last unfragmentable header (or the IP hdr, if none).
//
Reass->IPHdr.PayloadLength = net_short((ushort)DataLen);
RtlCopyMemory(ReassBuffer, (uchar *)&Reass->IPHdr, sizeof(IPv6Header));
RtlCopyMemory(ReassBuffer + memptr, Reass->UnfragData,
Reass->UnfragmentLength);
memptr += Reass->UnfragmentLength;
pNextHeader = ReassBuffer + Reass->NextHeaderOffset;
ASSERT(*pNextHeader == IP_PROTOCOL_FRAGMENT);
*pNextHeader = Reass->NextHeader;
//
// Run through the contiguous list, copying data over to our new packet.
//
PrevShim = ThisShim = Reass->ContigList;
while(ThisShim != NULL) {
RtlCopyMemory(ReassBuffer + memptr, PacketShimData(ThisShim),
ThisShim->Len);
memptr += ThisShim->Len;
if (memptr > TotalLength) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"ReassembleDatagram: packets don't add up\n"));
}
PrevShim = ThisShim;
ThisShim = ThisShim->Next;
ExFreePool(PrevShim);
}
//
// Initialize the reassembled packet structure.
//
RtlZeroMemory(ReassPacket, sizeof *ReassPacket);
AddRefIF(Reass->IF);
ReassPacket->NTEorIF = CastFromIF(Reass->IF);
ReassPacket->FlatData = ReassBuffer;
ReassPacket->Data = ReassBuffer;
ReassPacket->ContigSize = TotalLength;
ReassPacket->TotalSize = TotalLength;
ReassPacket->Flags = PACKET_HOLDS_REF | PACKET_REASSEMBLED |
(Reass->Flags & PACKET_INHERITED_FLAGS);
//
// Explicitly null out the ContigList which was freed above and
// clean up the reassembly struct. This also drops our lock
// on the reassembly struct.
//
Reass->ContigList = NULL;
DeleteFromReassemblyList(Reass);
IPSInfo.ipsi_reasmoks++;
//
// Receive the reassembled packet.
// If the current fragment was reassembled,
// then we should avoid another level of recursion.
// We must prevent "reassembly recursion".
// Test both paths in checked builds.
//
if ((Packet->Flags & PACKET_REASSEMBLED)
#if DBG
|| ((int)Random() < 0)
#endif
) {
ExInitializeWorkItem(&rrc->WQItem, ReassembledReceive, rrc);
ExQueueWorkItem(&rrc->WQItem, CriticalWorkQueue);
}
else {
int PktRefs = IPv6Receive(ReassPacket);
ASSERT(PktRefs == 0);
ExFreePool(rrc);
}
}
//* CreateFragmentPacket
//
// Recreates the first fragment packet for purposes of notifying a source
// of a 'fragment reassembly time exceeded'.
//
IPv6Packet *
CreateFragmentPacket(
Reassembly *Reass)
{
PacketShim *FirstFrag;
IPv6Packet *Packet;
FragmentHeader *FragHdr;
uint PayloadLength;
uint PacketLength;
uint MemLen;
uchar *Mem;
//
// There must be a first (offset-zero) fragment.
//
FirstFrag = Reass->ContigList;
ASSERT((FirstFrag != NULL) && (FirstFrag->Offset == 0));
//
// Allocate memory for creating the first fragment, i.e. the first
// buffer in our contig list. We include space for an IPv6Packet.
//
PayloadLength = (Reass->UnfragmentLength + sizeof(FragmentHeader) +
FirstFrag->Len);
PacketLength = sizeof(IPv6Header) + PayloadLength;
MemLen = sizeof(IPv6Packet) + PacketLength;
Mem = ExAllocatePool(NonPagedPool, MemLen);
if (Mem == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"CreateFragmentPacket: Couldn't allocate memory!?!\n"));
return NULL;
}
Packet = (IPv6Packet *) Mem;
Mem += sizeof(IPv6Packet);
Packet->Next = NULL;
Packet->IP = (IPv6Header UNALIGNED *) Mem;
Packet->IPPosition = 0;
Packet->Data = Packet->FlatData = Mem;
Packet->Position = 0;
Packet->ContigSize = Packet->TotalSize = PacketLength;
Packet->NdisPacket = NULL;
Packet->AuxList = NULL;
Packet->Flags = 0;
Packet->SrcAddr = AlignAddr(&Packet->IP->Source);
Packet->SAPerformed = NULL;
// Our caller must initialize Packet->NTEorIF.
AdjustPacketParams(Packet, sizeof(IPv6Header));
//
// Copy the original IPv6 header into the packet.
// Note that FragmentReceive ensures that
// Reass->IPHdr, Reass->UnfragData, and FirstFrag
// are all consistent.
//
RtlCopyMemory(Mem, (uchar *)&Reass->IPHdr, sizeof(IPv6Header));
Mem += sizeof(IPv6Header);
ASSERT(Reass->IPHdr.PayloadLength == net_short((ushort)PayloadLength));
//
// Copy the unfragmentable data into the packet.
//
RtlCopyMemory(Mem, Reass->UnfragData, Reass->UnfragmentLength);
Mem += Reass->UnfragmentLength;
//
// Create a fragment header in the packet.
//
FragHdr = (FragmentHeader *) Mem;
Mem += sizeof(FragmentHeader);
//
// Note that if the original offset-zero fragment had
// a non-zero value in the Reserved field, then we will
// not recreate it properly. It shouldn't do that.
//
FragHdr->NextHeader = Reass->NextHeader;
FragHdr->Reserved = 0;
FragHdr->OffsetFlag = net_short(FRAGMENT_FLAG_MASK);
FragHdr->Id = Reass->Id;
//
// Copy the original fragment data into the packet.
//
RtlCopyMemory(Mem, PacketShimData(FirstFrag), FirstFrag->Len);
return Packet;
}
//* ReassemblyTimeout - Handle a reassembly timer event.
//
// This routine is called periodically by IPv6Timeout to check for
// timed out fragments.
//
void
ReassemblyTimeout(void)
{
Reassembly *ThisReass, *NextReass;
Reassembly *Expired = NULL;
//
// Scan the ReassemblyList checking for expired reassembly contexts.
//
KeAcquireSpinLockAtDpcLevel(&ReassemblyList.Lock);
for (ThisReass = ReassemblyList.First;
ThisReass != SentinelReassembly;
ThisReass = NextReass) {
NextReass = ThisReass->Next;
//
// First decrement the timer then check if it has expired. If so,
// remove the reassembly record. This is basically the same code
// as in DeleteFromReassemblyList().
//
ThisReass->Timer--;
if (ThisReass->Timer == 0) {
RemoveReassembly(ThisReass);
KeAcquireSpinLockAtDpcLevel(&ThisReass->Lock);
ASSERT((ThisReass->State == REASSEMBLY_STATE_NORMAL) ||
(ThisReass->State == REASSEMBLY_STATE_DELETING));
if (ThisReass->State == REASSEMBLY_STATE_DELETING) {
//
// Note that we've removed it from the list already.
//
ThisReass->State = REASSEMBLY_STATE_REMOVED;
}
else {
//
// Move this reassembly context to the expired list.
// We must take a reference on the interface
// before releasing the reassembly record lock.
//
AddRefIF(ThisReass->IF);
ThisReass->Next = Expired;
Expired = ThisReass;
}
KeReleaseSpinLockFromDpcLevel(&ThisReass->Lock);
}
}
KeReleaseSpinLockFromDpcLevel(&ReassemblyList.Lock);
//
// Now that we no longer need the reassembly list lock,
// we can send ICMP errors at our leisure.
//
while ((ThisReass = Expired) != NULL) {
Interface *IF = ThisReass->IF;
#if DBG
char Buffer1[INET6_ADDRSTRLEN], Buffer2[INET6_ADDRSTRLEN];
FormatV6AddressWorker(Buffer1, &ThisReass->IPHdr.Source);
FormatV6AddressWorker(Buffer2, &ThisReass->IPHdr.Dest);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"ReassemblyTimeout: Src %s Dst %s Id %x\n",
Buffer1, Buffer2, ThisReass->Id));
#endif
Expired = ThisReass->Next;
//
// Send ICMP error IF we have received the first fragment.
// NB: Checking Marker != 0 is wrong, because we might have
// received a zero-length first fragment.
//
if (ThisReass->ContigList != NULL) {
IPv6Packet *Packet;
Packet = CreateFragmentPacket(ThisReass);
if (Packet != NULL) {
NetTableEntryOrInterface *NTEorIF;
ushort Type;
NTEorIF = FindAddressOnInterface(IF,
&ThisReass->IPHdr.Dest,
&Type);
if (NTEorIF != NULL) {
Packet->NTEorIF = NTEorIF;
ICMPv6SendError(Packet,
ICMPv6_TIME_EXCEEDED,
ICMPv6_REASSEMBLY_TIME_EXCEEDED, 0,
Packet->IP->NextHeader, FALSE);
if (IsNTE(NTEorIF))
ReleaseNTE(CastToNTE(NTEorIF));
else
ReleaseIF(CastToIF(NTEorIF));
}
ExFreePool(Packet);
}
}
//
// Delete the reassembly record.
//
ReleaseIF(IF);
DeleteReassembly(ThisReass);
}
}
//* DestinationOptionsReceive - Handle IPv6 Destination options.
//
// This is the routine called to process a Destination Options Header,
// a next header value of 60.
//
uchar
DestinationOptionsReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
IPv6OptionsHeader *DestOpt;
uint ExtLen;
Options Opts;
//
// Verify that we have enough contiguous data to overlay a Destination
// Options Header structure on the incoming packet. Then do so.
//
if (! PacketPullup(Packet, sizeof *DestOpt,
__builtin_alignof(IPv6OptionsHeader), 0)) {
if (Packet->TotalSize < sizeof *DestOpt) {
BadPayloadLength:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"DestinationOptionsReceive: Incoming packet too small"
" to contain destination options 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.
}
DestOpt = (IPv6OptionsHeader *) Packet->Data;
//
// Check that length of destination options also fit in remaining data.
// The options must also be aligned for any addresses in them.
//
ExtLen = (DestOpt->HeaderExtLength + 1) * EXT_LEN_UNIT;
if (! PacketPullup(Packet, ExtLen,
MAX(__builtin_alignof(IPv6OptionsHeader),
__builtin_alignof(IPv6Addr)), 0)) {
if (Packet->TotalSize < ExtLen)
goto BadPayloadLength;
return IP_PROTOCOL_NONE; // Drop packet.
}
DestOpt = (IPv6OptionsHeader *) Packet->Data;
//
// Remember offset to this header's NextHeader field.
//
Packet->NextHeaderPosition = Packet->Position +
FIELD_OFFSET(IPv6OptionsHeader, NextHeader);
//
// Skip over the extension header.
// We need to do this now so subsequent ICMP error generation works.
//
AdjustPacketParams(Packet, ExtLen);
//
// Parse options in this extension header. If an error occurs
// while parsing the options, discard packet.
//
if (!ParseOptions(Packet, IP_PROTOCOL_DEST_OPTS, DestOpt, ExtLen, &Opts)) {
return IP_PROTOCOL_NONE; // Drop packet.
}
//
// The processing of any additional options should be added here,
// before the home address option.
//
//
// Process the home address option.
//
if (Opts.HomeAddress) {
if (IPv6RecvHomeAddress(Packet, Opts.HomeAddress)) {
//
// Couldn't process the home address option. Drop the packet.
//
return IP_PROTOCOL_NONE;
}
}
//
// Process binding update option.
//
// Note that the Mobile IP spec says that the effects of processing the
// Home Address option should not be visible until all other options in
// the same Destination Options header have been processed. Although
// we process the Binding Update option after the Home Address option,
// we achieve the same effect by requiring IPv6RecvBindingUpdate to
// know that the Packet->SrcAddr has already been updated.
//
if (Opts.BindingUpdate) {
if (IPv6RecvBindingUpdate(Packet, Opts.BindingUpdate)) {
//
// Couldn't process the binding update. Drop the packet.
//
return IP_PROTOCOL_NONE;
}
}
//
// Return next header value.
//
return DestOpt->NextHeader;
}
//* HopByHopOptionsReceive - Handle a IPv6 Hop-by-Hop Options.
//
// This is the routine called to process a Hop-by-Hop Options Header,
// next header value of 0.
//
// Note that this routine is not a normal handler in the Protocol Switch
// Table. Instead, it receives special treatment in IPv6HeaderReceive.
// Because of this, it returns -1 instead of IP_PROTOCOL_NONE on error.
//
int
HopByHopOptionsReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
IPv6OptionsHeader *HopByHop;
uint ExtLen;
Options Opts;
//
// Verify that we have enough contiguous data to overlay a minimum
// length Hop-by-Hop Options Header. Then do so.
//
if (! PacketPullup(Packet, sizeof *HopByHop,
__builtin_alignof(IPv6OptionsHeader), 0)) {
if (Packet->TotalSize < sizeof *HopByHop) {
BadPayloadLength:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"HopByHopOptionsReceive: Incoming packet too small"
" to contain Hop-by-Hop Options header\n"));
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
IP_PROTOCOL_NONE, FALSE);
}
return -1; // Drop packet.
}
HopByHop = (IPv6OptionsHeader *) Packet->Data;
//
// Check that length of the Hop-by-Hop options also fits in remaining data.
// The options must also be aligned for any addresses in them.
//
ExtLen = (HopByHop->HeaderExtLength + 1) * EXT_LEN_UNIT;
if (! PacketPullup(Packet, ExtLen,
MAX(__builtin_alignof(IPv6OptionsHeader),
__builtin_alignof(IPv6Addr)), 0)) {
if (Packet->TotalSize < ExtLen)
goto BadPayloadLength;
return -1; // Drop packet.
}
HopByHop = (IPv6OptionsHeader *) Packet->Data;
//
// Remember offset to this header's NextHeader field.
//
Packet->NextHeaderPosition = Packet->Position +
FIELD_OFFSET(IPv6OptionsHeader, NextHeader);
//
// Skip over the extension header.
// We need to do this now so subsequent ICMP error generation works.
//
AdjustPacketParams(Packet, ExtLen);
//
// Parse options in this extension header. If an error occurs
// while parsing the options, discard packet.
//
if (!ParseOptions(Packet, IP_PROTOCOL_HOP_BY_HOP, HopByHop,
ExtLen, &Opts)) {
return -1; // Drop packet.
}
//
// If we have a valid Jumbo Payload Option, use its value as
// the packet PayloadLength.
//
if (Opts.JumboLength) {
uint PayloadLength = Opts.JumboLength;
ASSERT(Packet->IP->PayloadLength == 0);
//
// Check that the jumbo length is big enough to include
// the extension header length. This must be true because
// the extension-header length is at most 11 bits,
// while the jumbo length is at least 16 bits.
//
ASSERT(PayloadLength > ExtLen);
PayloadLength -= ExtLen;
//
// Check that the amount of payload specified in the Jumbo
// Payload value fits in the buffer handed to us.
//
if (PayloadLength > Packet->TotalSize) {
// Silently discard data.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"HopByHopOptionsReceive: "
"Jumbo payload length too big\n"));
return -1;
}
//
// As in IPv6HeaderReceive, adjust the TotalSize to be exactly the
// IP payload size (assume excess is media padding).
//
Packet->TotalSize = PayloadLength;
if (Packet->ContigSize > PayloadLength)
Packet->ContigSize = PayloadLength;
//
// Set the jumbo option packet flag.
//
Packet->Flags |= PACKET_JUMBO_OPTION;
}
else if (Packet->IP->PayloadLength == 0) {
//
// We should have a Jumbo Payload option,
// but we didn't find it. Send an ICMP error.
//
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
FIELD_OFFSET(IPv6Header, PayloadLength),
HopByHop->NextHeader, FALSE);
return -1;
}
//
// Return next header value.
//
return HopByHop->NextHeader;
}
//* ParseOptions - Routine for generic header options parsing.
//
// Returns TRUE if the options were successfully parsed.
// Returns FALSE if the packet should be discarded.
//
int
ParseOptions(
IPv6Packet *Packet, // The packet handed to us by IPv6Receive.
uchar HdrType, // Hop-by-hop or destination.
IPv6OptionsHeader *Hdr, // Header with following data.
uint HdrLength, // Length of the entire options area.
Options *Opts) // Return option values to caller.
{
uchar *OptPtr;
uint OptSizeLeft;
OptionHeader *OptHdr;
uint OptLen;
ASSERT((HdrType == IP_PROTOCOL_DEST_OPTS) ||
(HdrType == IP_PROTOCOL_HOP_BY_HOP));
//
// Zero out the Options struct that is returned.
//
RtlZeroMemory(Opts, sizeof *Opts);
//
// Skip over the extension header.
//
OptPtr = (uchar *)(Hdr + 1);
OptSizeLeft = HdrLength - sizeof *Hdr;
//
// Note that if there are multiple options
// of the same type, we just use the last one encountered
// unless the spec says specifically it is an error.
//
while (OptSizeLeft > 0) {
//
// First we check the option length and ensure that it fits.
// We move OptPtr past this option while leaving OptHdr
// for use by the option processing code below.
//
OptHdr = (OptionHeader *) OptPtr;
if (OptHdr->Type == OPT6_PAD_1) {
//
// This is a special pad option which is just a one byte field,
// i.e. it has no length or data field.
//
OptLen = 1;
}
else {
//
// This is a multi-byte option.
//
if ((sizeof *OptHdr > OptSizeLeft) ||
((OptLen = sizeof *OptHdr + OptHdr->DataLength) >
OptSizeLeft)) {
//
// Bad length, generate error and discard packet.
//
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&Hdr->HeaderExtLength) -
Packet->IPPosition),
Hdr->NextHeader, FALSE);
return FALSE;
}
}
OptPtr += OptLen;
OptSizeLeft -= OptLen;
switch (OptHdr->Type) {
case OPT6_PAD_1:
case OPT6_PAD_N:
break;
case OPT6_JUMBO_PAYLOAD:
if (HdrType != IP_PROTOCOL_HOP_BY_HOP)
goto BadOptionType;
if (OptHdr->DataLength != sizeof Opts->JumboLength)
goto BadOptionLength;
if (Packet->IP->PayloadLength != 0) {
//
// Jumbo option encountered when IP payload is not zero.
// Send ICMP error, set pointer to offset of this option type.
//
goto BadOptionType;
}
Opts->JumboLength = net_long(*(ulong UNALIGNED *)(OptHdr + 1));
if (Opts->JumboLength <= MAX_IPv6_PAYLOAD) {
//
// Jumbo payload length is not jumbo, send ICMP error.
// ICMP pointer is set to offset of jumbo payload len field.
//
goto BadOptionData;
}
break;
case OPT6_ROUTER_ALERT:
if (HdrType != IP_PROTOCOL_HOP_BY_HOP)
goto BadOptionType;
if (OptLen != sizeof *Opts->Alert)
goto BadOptionLength;
if (Opts->Alert != NULL) {
//
// Can only have one router alert option.
//
goto BadOptionType;
}
//
// Return the pointer to the router alert struct.
//
Opts->Alert = (IPv6RouterAlertOption UNALIGNED *)(OptHdr + 1);
break;
case OPT6_HOME_ADDRESS:
if (HdrType != IP_PROTOCOL_DEST_OPTS)
goto BadOptionType;
if (OptLen < sizeof *Opts->HomeAddress)
goto BadOptionLength;
//
// Return the pointer to the home address option
// after checking to make sure the address is reasonable.
// The option must be aligned so that the home address
// is appropriately aligned.
//
Opts->HomeAddress = (IPv6HomeAddressOption UNALIGNED *)OptHdr;
if (((UINT_PTR)&Opts->HomeAddress->HomeAddress % __builtin_alignof(IPv6Addr)) != 0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ParseOptions: misaligned home address\n"));
goto BadOptionType;
}
if (IsInvalidSourceAddress(AlignAddr(&Opts->HomeAddress->HomeAddress)) ||
IsUnspecified(AlignAddr(&Opts->HomeAddress->HomeAddress)) ||
IsLoopback(AlignAddr(&Opts->HomeAddress->HomeAddress))) {
//
// Address contained in option is invalid.
// Send ICMP error, set pointer to offset of home address.
//
goto BadOptionData;
}
break;
case OPT6_BINDING_UPDATE:
if (HdrType != IP_PROTOCOL_DEST_OPTS)
goto BadOptionType;
//
// At a minimum, the binding update must include all of the
// base header fields.
//
if (OptLen < sizeof(IPv6BindingUpdateOption)) {
//
// draft-ietf-mobileip-ipv6-13 sec 8.2 says we must
// silently drop the packet. Normally we would
// goto BadOptionLength to send an ICMP error.
//
return FALSE;
}
//
// Save pointer to the binding update option. Note we still
// need to do further length checking.
//
Opts->BindingUpdate = (IPv6BindingUpdateOption UNALIGNED *)OptHdr;
break;
default:
if (OPT6_ACTION(OptHdr->Type) == OPT6_A_SKIP) {
//
// Ignore the unrecognized option.
//
break;
}
else if (OPT6_ACTION(OptHdr->Type) == OPT6_A_DISCARD) {
//
// Discard the packet.
//
return FALSE;
}
else {
//
// Send an ICMP error.
//
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_UNRECOGNIZED_OPTION,
(GetPacketPositionFromPointer(Packet,
&OptHdr->Type) -
Packet->IPPosition),
Hdr->NextHeader,
OPT6_ACTION(OptHdr->Type) ==
OPT6_A_SEND_ICMP_ALL);
return FALSE; // discard the packet.
}
}
}
return TRUE;
BadOptionType:
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&OptHdr->Type) -
Packet->IPPosition),
Hdr->NextHeader, FALSE);
return FALSE; // discard packet.
BadOptionLength:
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&OptHdr->DataLength) -
Packet->IPPosition),
Hdr->NextHeader, FALSE);
return FALSE; // discard packet.
BadOptionData:
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
(uchar *)(OptHdr + 1)) -
Packet->IPPosition),
Hdr->NextHeader, FALSE);
return FALSE; // discard packet.
}
//* ExtHdrControlReceive - generic extension header skip-over routine.
//
// Routine for processing the extension headers in an ICMP error message
// before delivering the error message to the upper-layer protocol.
//
uchar
ExtHdrControlReceive(
IPv6Packet *Packet, // Packet handed to us by ICMPv6ErrorReceive.
StatusArg *StatArg) // ICMP Error code and offset pointer.
{
uchar NextHdr = StatArg->IP->NextHeader;
uint HdrLen;
for (;;) {
switch (NextHdr) {
case IP_PROTOCOL_HOP_BY_HOP:
case IP_PROTOCOL_DEST_OPTS:
case IP_PROTOCOL_ROUTING: {
ExtensionHeader *ExtHdr; // Generic exension header.
//
// Here we take advantage of the fact that all of these extension
// headers share the same first two fields (except as noted below).
// Since those two fields (Next Header and Header Extension Length)
// provide us with all the information we need to skip over the
// header, they're all we need to look at here.
//
if (! PacketPullup(Packet, sizeof *ExtHdr,
__builtin_alignof(ExtensionHeader), 0)) {
if (Packet->TotalSize < sizeof *ExtHdr) {
PacketTooSmall:
//
// Pullup failed. There isn't enough of the invoking
// packet included in the error message to figure out
// what upper layer protocol it originated with.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ExtHdrControlReceive: "
"Incoming ICMP error packet "
"doesn't contain enough of invoking packet\n"));
}
return IP_PROTOCOL_NONE; // Drop packet.
}
ExtHdr = (ExtensionHeader *) Packet->Data;
HdrLen = (ExtHdr->HeaderExtLength + 1) * EXT_LEN_UNIT;
//
// Now that we know the actual length of this extension header,
// skip over it.
//
// REVIEW: We could rework this to use PositionPacketAt
// REVIEW: here instead of PacketPullup as we don't need to
// REVIEW: look at the data we're skipping over. Better?
//
if (! PacketPullup(Packet, HdrLen, 1, 0)) {
if (Packet->TotalSize < HdrLen)
goto PacketTooSmall;
return IP_PROTOCOL_NONE; // Drop packet.
}
NextHdr = ExtHdr->NextHeader;
break;
}
case IP_PROTOCOL_FRAGMENT: {
FragmentHeader UNALIGNED *FragHdr;
if (! PacketPullup(Packet, sizeof *FragHdr, 1, 0)) {
if (Packet->TotalSize < sizeof *FragHdr)
goto PacketTooSmall;
return IP_PROTOCOL_NONE; // Drop packet.
}
FragHdr = (FragmentHeader UNALIGNED *) Packet->Data;
if ((net_short(FragHdr->OffsetFlag) & FRAGMENT_OFFSET_MASK) != 0) {
//
// We can only continue parsing if this
// fragment has offset zero.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ExtHdrControlReceive: "
"non-zero-offset fragment\n"));
return IP_PROTOCOL_NONE;
}
HdrLen = sizeof *FragHdr;
NextHdr = FragHdr->NextHeader;
break;
}
case IP_PROTOCOL_AH:
case IP_PROTOCOL_ESP:
//
// REVIEW - What is the correct thing here?
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"ExtHdrControlReceive: found AH/ESP\n"));
return IP_PROTOCOL_NONE;
default:
//
// We came to a header that we do not recognize,
// so we can not continue parsing here.
// But our caller might recognize this header type.
//
return NextHdr;
}
//
// Move past this extension header.
//
AdjustPacketParams(Packet, HdrLen);
}
}
//* RoutingReceive - Handle the IPv6 Routing Header.
//
// Called from IPv6Receive when we encounter a Routing Header,
// next header value of 43.
//
uchar
RoutingReceive(
IPv6Packet *Packet) // Packet handed to us by link layer.
{
IPv6RoutingHeader *RH;
uint HeaderLength;
uint SegmentsLeft;
uint NumAddresses, i;
IPv6Addr *Addresses;
IP_STATUS Status;
uchar *Mem;
uint MemLen, Offset;
NDIS_PACKET *FwdPacket;
NDIS_STATUS NdisStatus;
IPv6Header UNALIGNED *FwdIP;
IPv6RoutingHeader UNALIGNED *FwdRH;
IPv6Addr UNALIGNED *FwdAddresses;
IPv6Addr FwdDest;
int Delta;
uint PayloadLength;
uint TunnelStart = NO_TUNNEL, IPSecBytes = 0;
IPSecProc *IPSecToDo;
RouteCacheEntry *RCE;
uint Action;
//
// Verify that we have enough contiguous data,
// then get a pointer to the routing header.
//
if (! PacketPullup(Packet, sizeof *RH,
__builtin_alignof(IPv6RoutingHeader), 0)) {
if (Packet->TotalSize < sizeof *RH) {
BadPayloadLength:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"RoutingReceive: Incoming packet too small"
" to contain routing 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.
}
RH = (IPv6RoutingHeader *) Packet->Data;
//
// Now get the entire routing header.
// Also align for the address array.
//
HeaderLength = (RH->HeaderExtLength + 1) * EXT_LEN_UNIT;
if (! PacketPullup(Packet, HeaderLength,
MAX(__builtin_alignof(IPv6RoutingHeader),
__builtin_alignof(IPv6Addr)), 0)) {
if (Packet->TotalSize < HeaderLength)
goto BadPayloadLength;
return IP_PROTOCOL_NONE; // Drop packet.
}
RH = (IPv6RoutingHeader *) Packet->Data;
//
// Remember offset to this header's NextHeader field.
//
Packet->NextHeaderPosition = Packet->Position +
FIELD_OFFSET(IPv6RoutingHeader, NextHeader);
//
// Move past the routing header.
// We need to do this now so subsequent ICMP error generation works.
//
AdjustPacketParams(Packet, HeaderLength);
//
// If SegmentsLeft is zero, we proceed directly to the next header.
// We must not check the Type value or HeaderLength.
//
SegmentsLeft = RH->SegmentsLeft;
if (SegmentsLeft == 0) {
//
// Return next header value.
//
return RH->NextHeader;
}
//
// If we do not recognize the Type value, generate an ICMP error.
//
if (RH->RoutingType != 0) {
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&RH->RoutingType) -
Packet->IPPosition),
RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
//
// We must have an integral number of IPv6 addresses
// in the routing header.
//
if (RH->HeaderExtLength & 1) {
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&RH->HeaderExtLength) -
Packet->IPPosition),
RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
NumAddresses = RH->HeaderExtLength / 2;
//
// Sanity check SegmentsLeft.
//
if (SegmentsLeft > NumAddresses) {
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet,
&RH->SegmentsLeft) -
Packet->IPPosition),
RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
//
// Sanity check the destination address.
// Packets carrying a Type 0 Routing Header must not
// be sent to a multicast destination.
//
if (IsMulticast(AlignAddr(&Packet->IP->Dest))) {
//
// Just drop the packet, no ICMP error in this case.
//
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
i = NumAddresses - SegmentsLeft;
Addresses = AlignAddr((IPv6Addr UNALIGNED *) (RH + 1));
//
// Sanity check the new destination.
// RFC 2460 doesn't mention checking for an unspecified address,
// but I think it's a good idea. Similarly, for security reasons,
// we also check the scope of the destination. This allows
// applications to check the scope of the eventual destination address
// and know that the packet originated within that scope.
// RFC 2460 says to discard the packet without an ICMP error
// (at least when the new destination is multicast),
// but I think an ICMP error is helpful in this situation.
//
if (IsMulticast(&Addresses[i]) ||
IsUnspecified(&Addresses[i]) ||
(UnicastAddressScope(&Addresses[i]) <
UnicastAddressScope(AlignAddr(&Packet->IP->Dest)))) {
ICMPv6SendError(Packet,
ICMPv6_PARAMETER_PROBLEM,
ICMPv6_ERRONEOUS_HEADER_FIELD,
(GetPacketPositionFromPointer(Packet, (uchar *)
&Addresses[i]) -
Packet->IPPosition),
RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
//
// Verify IPSec was performed.
//
if (InboundSecurityCheck(Packet, 0, 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,
"RoutingReceive: "
"IPSec lookup failed or policy was to drop\n"));
return IP_PROTOCOL_NONE; // Drop packet.
}
//
// Find a route to the new destination.
//
Status = RouteToDestination(&Addresses[i],
0, Packet->NTEorIF,
RTD_FLAG_LOOSE, &RCE);
if (Status != IP_SUCCESS) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"RoutingReceive: "
"No route to destination for forwarding.\n"));
ICMPv6SendError(Packet,
ICMPv6_DESTINATION_UNREACHABLE,
ICMPv6_NO_ROUTE_TO_DESTINATION,
0, RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE;
}
//
// For security reasons, we prevent source routing
// in some situations. Check those now.
//
if (Packet->NTEorIF->IF->Flags & IF_FLAG_FORWARDS) {
//
// The interface is forwarding, so source-routing is allowed.
//
}
else if (Packet->NTEorIF->IF == RCE->NCE->IF) {
//
// Same-interface rule says source-routing is allowed,
// because the host is not acting as a conduit
// between two networks. See RFC 1122 section 3.3.5.
//
}
else if ((SegmentsLeft == 1) && RCE->NCE->IsLoopback) {
//
// The packet is locally destined, so source-routing is allowed.
// Mobile IPv6 uses the Routing Header in this way.
//
}
else {
//
// We can not allow this use of source-routing.
// Instead of reporting an error, we could
// redo RouteToDestination with RTD_FLAG_STRICT
// to constrain to the same interface.
// However, an ICMP error is more in keeping
// with the treatment of scoped source addresses,
// which can produce a destination-unreachable error.
//
ReleaseRCE(RCE);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"RoutingReceive: Inappropriate route.\n"));
ICMPv6SendError(Packet,
ICMPv6_DESTINATION_UNREACHABLE,
ICMPv6_COMMUNICATION_PROHIBITED,
0, RH->NextHeader, FALSE);
return IP_PROTOCOL_NONE;
}
//
// Find the Security Policy for this outbound traffic.
// The source address is the same but the destination address is the
// next hop from the routing header.
//
IPSecToDo = OutboundSPLookup(AlignAddr(&Packet->IP->Source),
&Addresses[i],
0, 0, 0, RCE->NCE->IF, &Action);
if (IPSecToDo == NULL) {
//
// Check Action.
//
if (Action == LOOKUP_DROP) {
// Drop packet.
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE;
} else {
if (Action == LOOKUP_IKE_NEG) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"RoutingReceive: IKE not supported yet.\n"));
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE;
}
}
//
// With no IPSec to perform, IPv6Forward won't be changing the
// outgoing interface from what we currently think it will be.
// So we can use the exact size of its link-level header.
//
Offset = RCE->NCE->IF->LinkHeaderSize;
} else {
//
// Calculate the space needed for the IPSec headers.
//
IPSecBytes = IPSecBytesToInsert(IPSecToDo, &TunnelStart, NULL);
if (TunnelStart != 0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"RoutingReceive: IPSec Tunnel mode only.\n"));
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE;
}
//
// The IPSec code in IPv6Forward might change the outgoing
// interface from what we currently think it will be. Play it
// safe and leave the max amount of space for its link-level header.
//
Offset = MAX_LINK_HEADER_SIZE;
}
//
// The packet has passed all our checks.
// We can construct a revised packet for transmission.
// First we allocate a packet, buffer, and memory.
//
// NB: The original packet is read-only for us. Furthermore
// we can not keep a pointer to it beyond the return of this
// function. So we must copy the packet and then modify it.
//
// Packet->IP->PayloadLength might be zero with jumbograms.
Delta = Packet->Position - Packet->IPPosition;
PayloadLength = Packet->TotalSize + Delta - sizeof(IPv6Header);
MemLen = Offset + sizeof(IPv6Header) + PayloadLength + IPSecBytes;
NdisStatus = IPv6AllocatePacket(MemLen, &FwdPacket, &Mem);
if (NdisStatus != NDIS_STATUS_SUCCESS) {
if (IPSecToDo) {
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
}
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}
FwdIP = (IPv6Header UNALIGNED *)(Mem + Offset + IPSecBytes);
FwdRH = (IPv6RoutingHeader UNALIGNED *)
((uchar *)FwdIP + Delta - HeaderLength);
FwdAddresses = (IPv6Addr UNALIGNED *) (FwdRH + 1);
//
// Now we copy from the original packet to the new packet.
//
CopyPacketToBuffer((uchar *)FwdIP, Packet,
sizeof(IPv6Header) + PayloadLength,
Packet->IPPosition);
//
// Fix up the new packet - put in the new destination address
// and decrement SegmentsLeft.
// NB: We pass the Reserved field through unmodified!
// This violates a strict reading of the spec,
// but Steve Deering has confirmed that this is his intent.
//
FwdDest = *AlignAddr(&FwdAddresses[i]);
*AlignAddr(&FwdAddresses[i]) = *AlignAddr(&FwdIP->Dest);
*AlignAddr(&FwdIP->Dest) = FwdDest;
FwdRH->SegmentsLeft--;
//
// Forward the packet. This decrements the Hop Limit and generates
// any applicable ICMP errors (Time Limit Exceeded, Destination
// Unreachable, Packet Too Big). Note that previous ICMP errors
// that we generated were based on the unmodified incoming packet,
// while from here on the ICMP errors are based on the new FwdPacket.
//
IPv6Forward(Packet->NTEorIF, FwdPacket, Offset + IPSecBytes, FwdIP,
PayloadLength, FALSE, // Don't Redirect.
IPSecToDo, RCE);
if (IPSecToDo) {
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
}
ReleaseRCE(RCE);
return IP_PROTOCOL_NONE; // No further processing of this packet.
}