You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3960 lines
130 KiB
3960 lines
130 KiB
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
|
|
//
|
|
// Copyright (c) 1998-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:
|
|
//
|
|
// Neighbor Discovery (ND) Protocol for Internet Protocol Version 6.
|
|
// Logically a part of ICMPv6, but put in a separate file for clarity.
|
|
// See RFC 2461 for details.
|
|
//
|
|
|
|
|
|
#include "oscfg.h"
|
|
#include "ndis.h"
|
|
#include "ip6imp.h"
|
|
#include "ip6def.h"
|
|
#include "icmp.h"
|
|
#include "neighbor.h"
|
|
#include "route.h"
|
|
#include "select.h"
|
|
#include "alloca.h"
|
|
#include "info.h"
|
|
|
|
//
|
|
//
|
|
// NeighborCacheLimit is an upper-bound on IF->NCENumUnused.
|
|
// NCENumUnused is the number of NCEs that have zero references.
|
|
// We do not keep track of the total number of NCEs,
|
|
// since NCEs with references are not available for reuse.
|
|
//
|
|
// REVIEW: What is a reasonable value for NeighborCacheLimit?
|
|
// Should probably be sized relative to physical memory
|
|
// and link characteristics.
|
|
//
|
|
// We cache & reclaim NCEs on a per-interface basis.
|
|
// Theoretically it would be better to use a global LRU list.
|
|
// However this would introduce added overhead (making NCEs bigger)
|
|
// and locking. At this point I believe that it isn't worth it.
|
|
//
|
|
// Another thought - it's much more important to support many RCEs
|
|
// than it is to support many NCEs.
|
|
//
|
|
uint NeighborCacheLimit; // Initialized in ConfigureGlobalParameters.
|
|
|
|
//* NeighborCacheInit
|
|
//
|
|
// Initialize the neighbor cache for an interface.
|
|
//
|
|
void
|
|
NeighborCacheInit(Interface *IF)
|
|
{
|
|
KeInitializeSpinLock(&IF->LockNC);
|
|
IF->FirstNCE = IF->LastNCE = SentinelNCE(IF);
|
|
ASSERT(IF->NCENumUnused == 0);
|
|
ASSERT(IF->PacketList == NULL);
|
|
}
|
|
|
|
//* NeighborCacheDestroy
|
|
//
|
|
// Cleanup and deallocate the NCEs in the neighbor cache.
|
|
//
|
|
// This is done when the interface is being destroyed
|
|
// and no one else has access to it, so it does not need to be locked.
|
|
//
|
|
void
|
|
NeighborCacheDestroy(Interface *IF)
|
|
{
|
|
NeighborCacheEntry *NCE;
|
|
PNDIS_PACKET Packet;
|
|
|
|
ASSERT(IsDisabledIF(IF));
|
|
ASSERT(IF->RefCnt == 0);
|
|
|
|
while ((NCE = IF->FirstNCE) != SentinelNCE(IF)) {
|
|
ASSERT(NCE->IF == IF);
|
|
ASSERT(NCE->RefCnt == 0);
|
|
|
|
//
|
|
// Unlink the NCE.
|
|
//
|
|
NCE->Next->Prev = NCE->Prev;
|
|
NCE->Prev->Next = NCE->Next;
|
|
InterlockedDecrement((long *)&IF->NCENumUnused);
|
|
|
|
//
|
|
// If there's a packet waiting, destroy it too.
|
|
//
|
|
Packet = NCE->WaitQueue;
|
|
if (Packet != NULL)
|
|
IPv6SendComplete(NULL, Packet, IP_GENERAL_FAILURE);
|
|
|
|
ExFreePool(NCE);
|
|
}
|
|
|
|
ASSERT(IF->NCENumUnused == 0);
|
|
}
|
|
|
|
|
|
//* NeighborCacheInitialize
|
|
//
|
|
// (Re)initialize a Neighbor Cache Entry.
|
|
//
|
|
// Our caller is responsible for using InvalidateRouteCache
|
|
// when appropriate.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
// (So we are at DPC level.)
|
|
//
|
|
void
|
|
NeighborCacheInitialize(
|
|
Interface *IF,
|
|
NeighborCacheEntry *NCE,
|
|
ushort NDState,
|
|
const void *LinkAddress)
|
|
{
|
|
//
|
|
// Forget everything we know about this neighbor.
|
|
//
|
|
|
|
NCE->IsRouter = FALSE;
|
|
NCE->IsUnreachable = FALSE;
|
|
NCE->DoRoundRobin = FALSE;
|
|
|
|
//
|
|
// Initialize this timestamp to a value in the past,
|
|
// so comparisons against it do not cause problems.
|
|
//
|
|
NCE->LastReachability = IPv6TickCount - IF->ReachableTime;
|
|
|
|
if (NDState == ND_STATE_INCOMPLETE) {
|
|
//
|
|
// Let the link-layer create an initial link-layer address
|
|
// and set the appropriate ND state.
|
|
//
|
|
NCE->NDState = (*IF->ConvertAddr)(IF->LinkContext,
|
|
&NCE->NeighborAddress,
|
|
NCE->LinkAddress);
|
|
}
|
|
else {
|
|
//
|
|
// Our caller supplied an ND state and a link-layer address.
|
|
//
|
|
NCE->NDState = NDState;
|
|
RtlCopyMemory(NCE->LinkAddress, LinkAddress, IF->LinkAddressLength);
|
|
}
|
|
|
|
if (NCE->NDState == ND_STATE_DELAY) {
|
|
//
|
|
// Internally we use the PROBE state with zero NSCount instead.
|
|
// If there is a packet waiting, we opt to send it immediately
|
|
// instead of waiting the usual delay.
|
|
//
|
|
NCE->NDState = ND_STATE_PROBE;
|
|
if (NCE->WaitQueue != NULL)
|
|
NCE->NSTimer = 1;
|
|
else
|
|
NCE->NSTimer = DELAY_FIRST_PROBE_TIME;
|
|
NCE->NSLimit = MAX_UNICAST_SOLICIT;
|
|
}
|
|
else if ((NCE->WaitQueue != NULL) ||
|
|
(NCE->NDState == ND_STATE_PROBE)) {
|
|
//
|
|
// Get NeighborCacheTimeout to do our dirty work.
|
|
// It will either send a solicitation (if the NCE is INCOMPLETE)
|
|
// or send the waiting packet directly.
|
|
// This is not a common case - not worth having code
|
|
// here using IPv6SendLater etc.
|
|
//
|
|
NCE->NSTimer = 1;
|
|
if (NCE->NDState == ND_STATE_INCOMPLETE)
|
|
NCE->NSLimit = MAX_MULTICAST_SOLICIT;
|
|
else if (NCE->NDState == ND_STATE_PROBE)
|
|
NCE->NSLimit = MAX_UNICAST_SOLICIT;
|
|
else
|
|
NCE->NSLimit = 0;
|
|
}
|
|
else {
|
|
//
|
|
// Cancel any pending timeout.
|
|
//
|
|
NCE->NSTimer = 0;
|
|
NCE->NSLimit = 0;
|
|
}
|
|
NCE->NSCount = 0;
|
|
}
|
|
|
|
|
|
//
|
|
//* AddRefNCEInCache
|
|
//
|
|
// Increments the reference count on an NCE
|
|
// in the interface's neighbor cache.
|
|
// The NCE's current reference count may be zero.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
// (So we are at DPC level.)
|
|
//
|
|
void
|
|
AddRefNCEInCache(NeighborCacheEntry *NCE)
|
|
{
|
|
//
|
|
// If the NCE previously had no references,
|
|
// increment the interface's reference count.
|
|
//
|
|
if (InterlockedIncrement(&NCE->RefCnt) == 1) {
|
|
Interface *IF = NCE->IF;
|
|
|
|
AddRefIF(IF);
|
|
InterlockedDecrement((long *)&IF->NCENumUnused);
|
|
}
|
|
}
|
|
|
|
//* ReleaseNCE
|
|
//
|
|
// Releases a reference for an NCE.
|
|
// May result in deallocation of the NCE.
|
|
//
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
ReleaseNCE(NeighborCacheEntry *NCE)
|
|
{
|
|
//
|
|
// If the NCE has no more references,
|
|
// release its reference for the interface.
|
|
// This may cause the interface (and hence the NCE)
|
|
// to be deallocated.
|
|
//
|
|
if (InterlockedDecrement(&NCE->RefCnt) == 0) {
|
|
Interface *IF = NCE->IF;
|
|
|
|
InterlockedIncrement((long *)&IF->NCENumUnused);
|
|
ReleaseIF(IF);
|
|
}
|
|
}
|
|
|
|
//* CreateOrReuseNeighbor
|
|
//
|
|
// Creates a new NCE for an interface.
|
|
// Attempts to reuse an existing NCE if the interface
|
|
// already has too many NCEs.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
// (So we are at DPC level.)
|
|
//
|
|
// Returns NULL if a new NCE could not be created.
|
|
// Returns the NCE with RefCnt, IF, LinkAddress, WaitQueue
|
|
// fields initialized. The NCE is last on the list
|
|
// and IF->NCENumUnused is incremented.
|
|
//
|
|
NeighborCacheEntry *
|
|
CreateOrReuseNeighbor(Interface *IF)
|
|
{
|
|
NeighborCacheEntry *NCE;
|
|
|
|
//
|
|
// If the cache is full, first try to free unused NCEs.
|
|
//
|
|
NeighborCacheCleanup(IF);
|
|
|
|
//
|
|
// FindNextHop tends to create NCEs that end up not getting used.
|
|
// We reuse these unused NCEs even when the cache is not full.
|
|
//
|
|
if (((NCE = IF->LastNCE) != SentinelNCE(IF)) &&
|
|
(NCE->RefCnt == 0) &&
|
|
(NCE->WaitQueue == NULL) &&
|
|
(((NCE->NDState == ND_STATE_INCOMPLETE) && !NCE->IsUnreachable) ||
|
|
(NCE->NDState == ND_STATE_PERMANENT)))
|
|
return NCE;
|
|
|
|
//
|
|
// Allocate a new NCE.
|
|
//
|
|
NCE = (NeighborCacheEntry *) ExAllocatePool(NonPagedPool, sizeof *NCE +
|
|
IF->LinkAddressLength);
|
|
if (NCE == NULL)
|
|
return NULL;
|
|
|
|
NCE->RefCnt = 0;
|
|
NCE->LinkAddress = (void *)(NCE + 1);
|
|
NCE->IF = IF;
|
|
NCE->WaitQueue = NULL;
|
|
|
|
//
|
|
// Link new entry into chain of NCEs on this interface.
|
|
// Put the new entry at the end, because until it is
|
|
// used to send a packet it has not proven itself valuable.
|
|
//
|
|
NCE->Prev = IF->LastNCE;
|
|
NCE->Prev->Next = NCE;
|
|
NCE->Next = SentinelNCE(IF);
|
|
NCE->Next->Prev = NCE;
|
|
InterlockedIncrement((long *)&IF->NCENumUnused);
|
|
|
|
return NCE;
|
|
}
|
|
|
|
//* NeighborCacheCleanup
|
|
//
|
|
// Removes unused NCEs if necessary to shrink the cache.
|
|
// Pushes unsent packets (that were waiting for address resolution)
|
|
// onto the interface's PacketList. Later NeighborCacheCompletePackets
|
|
// will be called to complete the packets.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
//
|
|
void
|
|
NeighborCacheCleanup(Interface *IF)
|
|
{
|
|
NDIS_PACKET *Packet;
|
|
NeighborCacheEntry *NCE;
|
|
NeighborCacheEntry *PrevNCE;
|
|
|
|
//
|
|
// We traverse the cache from back to front so that
|
|
// if we have to free unused NCEs, we free the LRU ones first.
|
|
//
|
|
for (NCE = IF->LastNCE;
|
|
(NCE != SentinelNCE(IF)) && (IF->NCENumUnused > NeighborCacheLimit);
|
|
NCE = PrevNCE) {
|
|
PrevNCE = NCE->Prev;
|
|
|
|
//
|
|
// Free the NCE if it is not being used.
|
|
//
|
|
if (NCE->RefCnt == 0) {
|
|
//
|
|
// If this NCE has a waiting packet,
|
|
// queue it for later completion.
|
|
//
|
|
Packet = NCE->WaitQueue;
|
|
if (Packet != NULL) {
|
|
PC(Packet)->pc_drop = TRUE;
|
|
PC(Packet)->pc_link = IF->PacketList;
|
|
IF->PacketList = Packet;
|
|
}
|
|
|
|
//
|
|
// Remove and release the unused NCE.
|
|
// It does not hold a reference for the interface.
|
|
//
|
|
NCE->Next->Prev = NCE->Prev;
|
|
NCE->Prev->Next = NCE->Next;
|
|
InterlockedDecrement((long *)&IF->NCENumUnused);
|
|
ExFreePool(NCE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//* NeighborCacheCompletePackets
|
|
//
|
|
// Completes a list of packets that had been waiting for address resolution.
|
|
//
|
|
// Called with no locks held.
|
|
//
|
|
void
|
|
NeighborCacheCompletePackets(
|
|
Interface *IF,
|
|
NDIS_PACKET *PacketList)
|
|
{
|
|
NDIS_PACKET *Packet;
|
|
|
|
while ((Packet = PacketList) != NULL) {
|
|
PacketList = PC(Packet)->pc_link;
|
|
|
|
if (PC(Packet)->pc_drop) {
|
|
//
|
|
// Drop the packet because of resource shortage.
|
|
//
|
|
IPv6SendComplete(NULL, Packet, IP_NO_RESOURCES);
|
|
}
|
|
else {
|
|
//
|
|
// Abort the packet because address resolution failed.
|
|
//
|
|
IPv6SendAbort(CastFromIF(IF),
|
|
Packet, PC(Packet)->pc_offset,
|
|
ICMPv6_DESTINATION_UNREACHABLE,
|
|
ICMPv6_ADDRESS_UNREACHABLE,
|
|
0, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//* FindOrCreateNeighbor
|
|
//
|
|
// Searches an interface's neighbor cache for an entry.
|
|
// Creates the entry (but does NOT send initial solicit)
|
|
// if an existing entry is not found.
|
|
//
|
|
// May be called with route cache lock or interface lock held.
|
|
// Callable from thread or DPC context.
|
|
//
|
|
// We avoid sending a solicit here, because this function
|
|
// is called while holding locks that make that a bad idea.
|
|
//
|
|
// Returns NULL only if an NCE could not be created.
|
|
//
|
|
NeighborCacheEntry *
|
|
FindOrCreateNeighbor(Interface *IF, const IPv6Addr *Addr)
|
|
{
|
|
KIRQL OldIrql;
|
|
NeighborCacheEntry *NCE;
|
|
|
|
KeAcquireSpinLock(&IF->LockNC, &OldIrql);
|
|
|
|
for (NCE = IF->FirstNCE; NCE != SentinelNCE(IF); NCE = NCE->Next) {
|
|
|
|
if (IP6_ADDR_EQUAL(Addr, &NCE->NeighborAddress)) {
|
|
//
|
|
// Found matching entry.
|
|
//
|
|
goto ReturnNCE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get a new entry for this neighbor.
|
|
//
|
|
NCE = CreateOrReuseNeighbor(IF);
|
|
if (NCE == NULL) {
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
return NULL;
|
|
}
|
|
|
|
ASSERT(IF->LastNCE == NCE);
|
|
ASSERT(NCE->RefCnt == 0);
|
|
|
|
//
|
|
// Initialize the neighbor cache entry.
|
|
// The RefCnt, IF, LinkAddress, WaitQueue fields are already initialized.
|
|
//
|
|
NCE->NeighborAddress = *Addr;
|
|
#if DBG
|
|
RtlZeroMemory(NCE->LinkAddress, IF->LinkAddressLength);
|
|
#endif
|
|
|
|
//
|
|
// Initialize this NCE normally.
|
|
// Loopback initialization happens in ControlNeighborLoopback.
|
|
//
|
|
NCE->IsLoopback = FALSE;
|
|
NeighborCacheInitialize(IF, NCE, ND_STATE_INCOMPLETE, NULL);
|
|
|
|
ReturnNCE:
|
|
AddRefNCEInCache(NCE);
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
return NCE;
|
|
}
|
|
|
|
|
|
//* ControlNeighborLoopback
|
|
//
|
|
// Sets the Neighbor Discovery state of an NCE
|
|
// to control loopback behavior.
|
|
//
|
|
// Called with the interface locked.
|
|
// (So we are at DPC level.)
|
|
//
|
|
void
|
|
ControlNeighborLoopback(
|
|
NeighborCacheEntry *NCE,
|
|
int Loopback)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
|
|
if (Loopback) {
|
|
//
|
|
// Initialize this NCE for loopback.
|
|
//
|
|
NCE->IsLoopback = TRUE;
|
|
NeighborCacheInitialize(IF, NCE, ND_STATE_PERMANENT, IF->LinkAddress);
|
|
}
|
|
else {
|
|
//
|
|
// Initialize this NCE normally.
|
|
//
|
|
NCE->IsLoopback = FALSE;
|
|
NeighborCacheInitialize(IF, NCE, ND_STATE_INCOMPLETE, NULL);
|
|
}
|
|
|
|
//
|
|
// We changed state that affects routing.
|
|
//
|
|
InvalidateRouteCache();
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
}
|
|
|
|
//* GetReachability
|
|
//
|
|
// Returns reachability information for a neighbor.
|
|
//
|
|
// Because FindNextHop uses GetReachability, any state change
|
|
// that changes GetReachability's return value
|
|
// must invalidate the route cache.
|
|
//
|
|
// The NeighborRoundRobin return value is special - it indicates
|
|
// that FindNextHop should round-robin and use a different route.
|
|
// It is not persistent - a subsequent call to GetReachability
|
|
// will return NeighborUnreachable.
|
|
//
|
|
// Callable from DPC context (or with the route lock held),
|
|
// not from thread context.
|
|
//
|
|
int
|
|
GetReachability(NeighborCacheEntry *NCE)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
NeighborReachability Reachable;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
if (IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)
|
|
Reachable = NeighborInterfaceDisconnected;
|
|
else if (NCE->IsUnreachable) {
|
|
if (NCE->DoRoundRobin) {
|
|
NCE->DoRoundRobin = FALSE;
|
|
Reachable = NeighborRoundRobin;
|
|
} else
|
|
Reachable = NeighborUnreachable;
|
|
} else
|
|
Reachable = NeighborMayBeReachable;
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
|
|
return Reachable;
|
|
}
|
|
|
|
//* NeighborCacheUpdate - Update link address information about a neighbor.
|
|
//
|
|
// Called when we've received possibly new information about one of our
|
|
// neighbors, the source of a Neighbor Solicitation, Router Advertisement,
|
|
// or Router Solicitation.
|
|
//
|
|
// Note that our receipt of this packet DOES NOT imply forward reachability
|
|
// to this neighbor, so we do not update our LastReachability timer.
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
// The LinkAddress might be NULL, which means we can only process IsRouter.
|
|
//
|
|
// If IsRouter is FALSE, then we don't know whether the neighbor
|
|
// is a router or not. If it's TRUE, then we know it is a router.
|
|
//
|
|
void
|
|
NeighborCacheUpdate(NeighborCacheEntry *NCE, // The neighbor.
|
|
const void *LinkAddress, // Corresponding media address.
|
|
int IsRouter) // Do we know it's a router.
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
PNDIS_PACKET Packet = NULL;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
|
|
if (NCE->NDState != ND_STATE_PERMANENT) {
|
|
//
|
|
// Check to see if the link address changed.
|
|
//
|
|
if ((LinkAddress != NULL) &&
|
|
((NCE->NDState == ND_STATE_INCOMPLETE) ||
|
|
RtlCompareMemory(LinkAddress, NCE->LinkAddress,
|
|
IF->LinkAddressLength) != IF->LinkAddressLength)) {
|
|
//
|
|
// Link-level address changed. Update cache entry with the
|
|
// new one and change state to STALE as we haven't verified
|
|
// forward reachability with the new address yet.
|
|
//
|
|
RtlCopyMemory(NCE->LinkAddress, LinkAddress,
|
|
IF->LinkAddressLength);
|
|
NCE->NSTimer = 0; // Cancel any outstanding timeout.
|
|
NCE->NDState = ND_STATE_STALE;
|
|
|
|
//
|
|
// Flush the queue of waiting packets.
|
|
// (Only relevant if we were in the INCOMPLETE state.)
|
|
//
|
|
if (NCE->WaitQueue != NULL) {
|
|
|
|
Packet = NCE->WaitQueue;
|
|
NCE->WaitQueue = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we know that the neighbor is a router,
|
|
// remember that fact.
|
|
//
|
|
if (IsRouter)
|
|
NCE->IsRouter = TRUE;
|
|
|
|
} // end if (NCE->NDState != ND_STATE_PERMANENT)
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
|
|
//
|
|
// If we can now send a packet, do so.
|
|
// (Without holding a lock.)
|
|
//
|
|
if (Packet != NULL) {
|
|
uint Offset;
|
|
|
|
Offset = PC(Packet)->pc_offset;
|
|
IPv6SendND(Packet, Offset, NCE, &(PC(Packet)->DiscoveryAddress));
|
|
}
|
|
}
|
|
|
|
//* NeighborCacheSearch
|
|
//
|
|
// Searches the neighbor cache for an entry that matches
|
|
// the neighbor IPv6 address. If found, returns the link-level address.
|
|
// Returns FALSE to indicate failure.
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
int
|
|
NeighborCacheSearch(
|
|
Interface *IF,
|
|
const IPv6Addr *Neighbor,
|
|
void *LinkAddress)
|
|
{
|
|
NeighborCacheEntry *NCE;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
|
|
for (NCE = IF->FirstNCE; NCE != SentinelNCE(IF); NCE = NCE->Next) {
|
|
|
|
if (IP6_ADDR_EQUAL(Neighbor, &NCE->NeighborAddress)) {
|
|
//
|
|
// Entry found. Return it's cached link-address,
|
|
// if it's valid.
|
|
//
|
|
if (NCE->NDState == ND_STATE_INCOMPLETE) {
|
|
//
|
|
// No valid link address.
|
|
//
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The entry has a link-level address.
|
|
// Must copy it with the lock held.
|
|
//
|
|
RtlCopyMemory(LinkAddress, NCE->LinkAddress,
|
|
IF->LinkAddressLength);
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
return FALSE;
|
|
}
|
|
|
|
//* NeighborCacheAdvert
|
|
//
|
|
// Updates the neighbor cache in response to an advertisement.
|
|
// If no matching entry is found, ignores the advertisement.
|
|
// (See RFC 1970 section 7.2.5.)
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
void
|
|
NeighborCacheAdvert(
|
|
Interface *IF,
|
|
const IPv6Addr *TargetAddress,
|
|
const void *LinkAddress,
|
|
ulong Flags)
|
|
{
|
|
NeighborCacheEntry *NCE;
|
|
PNDIS_PACKET Packet = NULL;
|
|
int PurgeRouting = FALSE;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
|
|
for (NCE = IF->FirstNCE; NCE != SentinelNCE(IF); NCE = NCE->Next) {
|
|
if (IP6_ADDR_EQUAL(TargetAddress, &NCE->NeighborAddress)) {
|
|
|
|
if (NCE->NDState != ND_STATE_PERMANENT) {
|
|
//
|
|
// Pick up link-level address from the advertisement,
|
|
// if we don't have one yet or if override is set.
|
|
//
|
|
if ((LinkAddress != NULL) &&
|
|
((NCE->NDState == ND_STATE_INCOMPLETE) ||
|
|
((Flags & ND_NA_FLAG_OVERRIDE) &&
|
|
RtlCompareMemory(LinkAddress, NCE->LinkAddress,
|
|
IF->LinkAddressLength) != IF->LinkAddressLength))) {
|
|
|
|
RtlCopyMemory(NCE->LinkAddress, LinkAddress,
|
|
IF->LinkAddressLength);
|
|
|
|
NCE->NSTimer = 0; // Cancel any outstanding timeout.
|
|
NCE->NDState = ND_STATE_STALE;
|
|
|
|
//
|
|
// Flush the queue of waiting packets.
|
|
//
|
|
if (NCE->WaitQueue != NULL) {
|
|
|
|
Packet = NCE->WaitQueue;
|
|
NCE->WaitQueue = NULL;
|
|
|
|
//
|
|
// Need to keep ref on NCE after we unlock.
|
|
//
|
|
AddRefNCEInCache(NCE);
|
|
}
|
|
|
|
goto AdvertisementMatchesCachedAddress;
|
|
}
|
|
|
|
if ((NCE->NDState != ND_STATE_INCOMPLETE) &&
|
|
((LinkAddress == NULL) ||
|
|
RtlCompareMemory(LinkAddress, NCE->LinkAddress,
|
|
IF->LinkAddressLength) == IF->LinkAddressLength)) {
|
|
ushort WasRouter;
|
|
|
|
AdvertisementMatchesCachedAddress:
|
|
|
|
//
|
|
// If this is a solicited advertisement
|
|
// for our cached link-layer address,
|
|
// then we have confirmed reachability.
|
|
//
|
|
if (Flags & ND_NA_FLAG_SOLICITED) {
|
|
NCE->NSTimer = 0; // Cancel any outstanding timeout.
|
|
NCE->NSCount = 0;
|
|
NCE->LastReachability = IPv6TickCount; // Timestamp it.
|
|
NCE->NDState = ND_STATE_REACHABLE;
|
|
|
|
if (NCE->IsUnreachable) {
|
|
//
|
|
// We had previously concluded that this neighbor
|
|
// is unreachable. Now we know otherwise.
|
|
//
|
|
NCE->IsUnreachable = FALSE;
|
|
InvalidateRouteCache();
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is an advertisement
|
|
// for our cached link-layer address,
|
|
// then update IsRouter.
|
|
//
|
|
WasRouter = NCE->IsRouter;
|
|
NCE->IsRouter = ((Flags & ND_NA_FLAG_ROUTER) != 0);
|
|
if (WasRouter && !NCE->IsRouter) {
|
|
//
|
|
// This neighbor used to be a router, but is no longer.
|
|
//
|
|
PurgeRouting = TRUE;
|
|
|
|
//
|
|
// Need to keep ref on NCE after we unlock.
|
|
//
|
|
AddRefNCEInCache(NCE);
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// This is not an advertisement
|
|
// for our cached link-layer address.
|
|
// If the advertisement was unsolicited,
|
|
// give NUD a little nudge.
|
|
//
|
|
if (Flags & ND_NA_FLAG_SOLICITED) {
|
|
//
|
|
// This is probably a second NA in response
|
|
// to our multicast NS for an anycast address.
|
|
//
|
|
}
|
|
else {
|
|
if (NCE->NDState == ND_STATE_REACHABLE)
|
|
NCE->NDState = ND_STATE_STALE;
|
|
}
|
|
}
|
|
} // end if (NCE->NDState != ND_STATE_PERMANENT)
|
|
|
|
//
|
|
// Only one NCE should match.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
|
|
//
|
|
// If we can now send a packet, do so.
|
|
// (Without holding a lock.)
|
|
//
|
|
// It is possible that this neighbor is no longer a router,
|
|
// and the waiting packet wants to use the neighbor as a router.
|
|
// In this situation the ND spec requires that we still send
|
|
// the waiting packet to the neighbor. Narten/Nordmark confirmed
|
|
// this interpretation in private email.
|
|
//
|
|
if (Packet != NULL) {
|
|
uint Offset = PC(Packet)->pc_offset;
|
|
IPv6SendND(Packet, Offset, NCE, &(PC(Packet)->DiscoveryAddress));
|
|
ReleaseNCE(NCE);
|
|
}
|
|
|
|
//
|
|
// If need be, purge the routing data structures.
|
|
//
|
|
if (PurgeRouting) {
|
|
InvalidateRouter(NCE);
|
|
ReleaseNCE(NCE);
|
|
}
|
|
}
|
|
|
|
//* NeighborCacheProbeUnreachability
|
|
//
|
|
// Initiates an active probe of an unreachable neighbor,
|
|
// to determine if the neighbor is still unreachable.
|
|
//
|
|
// To prevent ourselves from probing too frequently,
|
|
// the first probe is scheduled after waiting at least
|
|
// UNREACH_SOLICIT_INTERVAL from when we last determined
|
|
// this neighbor to be unreachable. If called again in this
|
|
// interval, we do nothing.
|
|
//
|
|
// Callable from DPC context (or with the route lock held),
|
|
// not from thread context.
|
|
//
|
|
void
|
|
NeighborCacheProbeUnreachability(NeighborCacheEntry *NCE)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
uint Elapsed;
|
|
ushort Delay;
|
|
|
|
if (!(IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS)) {
|
|
//
|
|
// We cannot probe on interfaces that do not support ND.
|
|
//
|
|
return;
|
|
}
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED) && NCE->IsUnreachable) {
|
|
//
|
|
// Because the NCE is unreachable, we can not be in
|
|
// the REACHABLE or PERMANENT states. The non-INCOMPLETE
|
|
// states are possible if the NCE is unreachable/INCOMPLETE
|
|
// and then we receive passive information in NeighborCacheUpdate.
|
|
//
|
|
ASSERT((NCE->NDState == ND_STATE_INCOMPLETE) ||
|
|
(NCE->NDState == ND_STATE_PROBE) ||
|
|
(NCE->NDState == ND_STATE_STALE));
|
|
|
|
//
|
|
// Calculate the appropriate delay until we can probe
|
|
// unreachability. We do not want to determine unreachability
|
|
// more frequently than UNREACH_SOLICIT_INTERVAL.
|
|
//
|
|
Elapsed = IPv6TickCount - NCE->LastReachability;
|
|
if (Elapsed < UNREACH_SOLICIT_INTERVAL)
|
|
Delay = (ushort) (UNREACH_SOLICIT_INTERVAL - Elapsed);
|
|
else
|
|
Delay = 1;
|
|
|
|
//
|
|
// If we are not already soliciting this neighbor,
|
|
// probe the neighbor to check if it's still unreachable.
|
|
//
|
|
if (NCE->NDState == ND_STATE_STALE) {
|
|
//
|
|
// We need to be in the PROBE state to actively probe reachability.
|
|
//
|
|
NCE->NDState = ND_STATE_PROBE;
|
|
ASSERT(NCE->NSTimer == 0);
|
|
goto ProbeReachability;
|
|
}
|
|
else if ((NCE->NDState == ND_STATE_INCOMPLETE) &&
|
|
(NCE->NSTimer == 0)) {
|
|
ProbeReachability:
|
|
//
|
|
// NeighborCacheEntryTimeout will send the first probe.
|
|
//
|
|
NCE->NSLimit = MAX_UNREACH_SOLICIT;
|
|
NCE->NSTimer = Delay;
|
|
}
|
|
else {
|
|
//
|
|
// We are already in the PROBE or active INCOMPLETE states.
|
|
// First, check NSLimit. It might be MAX_UNICAST_SOLICIT or
|
|
// MAX_MULTICAST_SOLICIT. Ensure it's at least MAX_UNICAST_SOLICIT.
|
|
//
|
|
if (NCE->NSLimit < MAX_UNREACH_SOLICIT)
|
|
NCE->NSLimit = MAX_UNREACH_SOLICIT;
|
|
//
|
|
// Second, if we have not started actively probing yet, ensure
|
|
// we do not wait longer than Delay to start.
|
|
//
|
|
if ((NCE->NSCount == 0) && (NCE->NSTimer > Delay))
|
|
NCE->NSTimer = Delay;
|
|
}
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
}
|
|
|
|
//* NeighborCacheReachabilityConfirmation
|
|
//
|
|
// Updates the neighbor cache entry in response to an indication
|
|
// of forward reachability. This indication is from an upper layer
|
|
// (for example, receipt of a reply to a request).
|
|
//
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
NeighborCacheReachabilityConfirmation(NeighborCacheEntry *NCE)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IF->LockNC, &OldIrql);
|
|
|
|
switch (NCE->NDState) {
|
|
case ND_STATE_INCOMPLETE:
|
|
//
|
|
// This is strange. Perhaps the reachability confirmation is
|
|
// arriving very late and ND has already decided the neighbor
|
|
// is unreachable? Or perhaps the upper-layer protocol is just
|
|
// mistaken? In any case ignore the confirmation.
|
|
//
|
|
break;
|
|
|
|
case ND_STATE_PROBE:
|
|
//
|
|
// Stop sending solicitations.
|
|
//
|
|
NCE->NSCount = 0;
|
|
NCE->NSTimer = 0;
|
|
// fall-through
|
|
|
|
case ND_STATE_STALE:
|
|
//
|
|
// We have forward reachability.
|
|
//
|
|
NCE->NDState = ND_STATE_REACHABLE;
|
|
|
|
if (NCE->IsUnreachable) {
|
|
//
|
|
// We can get here if an NCE is reachable but goes INCOMPLETE.
|
|
// Then we later receive passive information and the state
|
|
// changes to STALE. Then we receive upper-layer confirmation
|
|
// that the neighbor is reachable again.
|
|
//
|
|
// We had previously concluded that this neighbor
|
|
// is unreachable. Now we know otherwise.
|
|
//
|
|
NCE->IsUnreachable = FALSE;
|
|
InvalidateRouteCache();
|
|
}
|
|
// fall-through
|
|
|
|
case ND_STATE_REACHABLE:
|
|
//
|
|
// Timestamp this reachability confirmation.
|
|
//
|
|
NCE->LastReachability = IPv6TickCount;
|
|
// fall-through
|
|
|
|
case ND_STATE_PERMANENT:
|
|
//
|
|
// Ignore the confirmation.
|
|
//
|
|
ASSERT(! NCE->IsUnreachable);
|
|
break;
|
|
|
|
default:
|
|
ABORTMSG("Invalid ND state?");
|
|
break;
|
|
}
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
}
|
|
|
|
//* NeighborCacheReachabilityInDoubt
|
|
//
|
|
// Updates the neighbor cache entry in response to an indication
|
|
// from an upper-layer protocol that the neighbor may not be reachable.
|
|
// (For example, a reply to a request was not received.)
|
|
//
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
NeighborCacheReachabilityInDoubt(NeighborCacheEntry *NCE)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IF->LockNC, &OldIrql);
|
|
|
|
if (NCE->NDState == ND_STATE_REACHABLE)
|
|
NCE->NDState = ND_STATE_STALE;
|
|
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
}
|
|
|
|
typedef struct NeighborCacheEntrySolicitInfo {
|
|
struct NeighborCacheEntrySolicitInfo *Next;
|
|
NeighborCacheEntry *NCE; // Holds a reference.
|
|
const IPv6Addr *DiscoveryAddress; // NULL or points to AddrBuf.
|
|
IPv6Addr AddrBuf;
|
|
} NeighborCacheEntrySolicitInfo;
|
|
|
|
//* NeighborCacheEntrySendSolicitHelper
|
|
//
|
|
// Helper function for sending a solicit for an NCE
|
|
// when NeighborSolicitSend can not be used.
|
|
// Allocates an info structure (which holds an NCE reference)
|
|
// and pushes the structure on the list.
|
|
// The list holds information about deferred solicits.
|
|
// Later the list is processed and solicitations sent.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
// (So we are at DPC level.)
|
|
//
|
|
void
|
|
NeighborCacheEntrySendSolicitHelper(
|
|
NeighborCacheEntry *NCE,
|
|
NeighborCacheEntrySolicitInfo **pInfoList)
|
|
{
|
|
NeighborCacheEntrySolicitInfo *Info;
|
|
|
|
//
|
|
// If this allocation fails we just skip this solicitation.
|
|
//
|
|
Info = ExAllocatePool(NonPagedPool, sizeof *Info);
|
|
if (Info != NULL) {
|
|
NDIS_PACKET *WaitPacket;
|
|
|
|
AddRefNCEInCache(NCE);
|
|
Info->NCE = NCE;
|
|
|
|
//
|
|
// If we have a packet waiting for address resolution,
|
|
// then take the source address for the solicit
|
|
// from the waiting packet.
|
|
//
|
|
WaitPacket = NCE->WaitQueue;
|
|
if (WaitPacket != NULL) {
|
|
Info->DiscoveryAddress = &Info->AddrBuf;
|
|
Info->AddrBuf = PC(WaitPacket)->DiscoveryAddress;
|
|
} else {
|
|
Info->DiscoveryAddress = NULL;
|
|
}
|
|
|
|
Info->Next = *pInfoList;
|
|
*pInfoList = Info;
|
|
}
|
|
}
|
|
|
|
//* NeighborCacheEntryTimeout - handle an event timeout on an NCE.
|
|
//
|
|
// NeighborCacheTimeout calls this routine when
|
|
// an NCE's NSTimer expires.
|
|
//
|
|
// Called with the neighbor cache lock held.
|
|
// (So we are at DPC level.)
|
|
//
|
|
// We can not call NeighborSolicitSend or IPv6SendAbort
|
|
// directly, because we hold the neighbor cache lock.
|
|
// For NeighborSolicitSend we use NeighborCacheEntrySendSolicitHelper
|
|
// to defer the solicit until later, and for IPv6SendAbort
|
|
// we push the packet on the interface's PacketList and
|
|
// NeighborCacheCompletePackets handles it later.
|
|
// However we make all the NCE state transitions directly here,
|
|
// so they will happen in a timely fashion.
|
|
//
|
|
// NB: We do not want to punt NeighborSolicitSend to a worker thread,
|
|
// because DPC activity preempts worker threads. Prolonged activity
|
|
// at DPC level (for example a DoS attack) would prevent solicits
|
|
// from being sent, and more importantly, would prevent NCEs from
|
|
// being recycled because the work items would hold NCE references.
|
|
//
|
|
void
|
|
NeighborCacheEntryTimeout(
|
|
NeighborCacheEntry *NCE,
|
|
NeighborCacheEntrySolicitInfo **pInfoList)
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
NDIS_PACKET *Packet;
|
|
NDIS_STATUS Status;
|
|
|
|
//
|
|
// Neighbor Discovery has timeouts for initial neighbor
|
|
// solicitation retransmissions, the delay state, and probe
|
|
// neighbor solicitation retransmissions. All of these share
|
|
// the same NSTimer, and are distinguished from each other
|
|
// by the NDState.
|
|
//
|
|
switch (NCE->NDState) {
|
|
case ND_STATE_INCOMPLETE:
|
|
//
|
|
// Retransmission timer expired. Check to see if
|
|
// sending another solicit would exceed the maximum.
|
|
//
|
|
if (NCE->NSCount >= NCE->NSLimit) {
|
|
//
|
|
// Failed to initiate connectivity to neighbor.
|
|
// Reset to dormant INCOMPLETE state.
|
|
//
|
|
NCE->NSCount = 0;
|
|
if (NCE->WaitQueue != NULL) {
|
|
//
|
|
// Remove the waiting packet from the NCE
|
|
// and let NeighborCacheTimeout handle it.
|
|
// We can't call IPv6SendAbort directly
|
|
// because we hold the neighbor cache lock.
|
|
//
|
|
Packet = NCE->WaitQueue;
|
|
NCE->WaitQueue = NULL;
|
|
PC(Packet)->pc_drop = FALSE;
|
|
PC(Packet)->pc_link = IF->PacketList;
|
|
IF->PacketList = Packet;
|
|
}
|
|
|
|
//
|
|
// This neighbor is not reachable.
|
|
// IsUnreachable may already be TRUE.
|
|
// But we need to give FindNextHop an opportunity to round-robin.
|
|
//
|
|
NCE->IsUnreachable = TRUE;
|
|
NCE->LastReachability = IPv6TickCount; // Timestamp it.
|
|
NCE->DoRoundRobin = TRUE;
|
|
InvalidateRouteCache();
|
|
}
|
|
else {
|
|
//
|
|
// Retransmit initial solicit, taking source address
|
|
// from the waiting packet.
|
|
//
|
|
NCE->NSCount++;
|
|
NeighborCacheEntrySendSolicitHelper(NCE, pInfoList);
|
|
|
|
//
|
|
// Re-arm timer for the next solicitation.
|
|
//
|
|
NCE->NSTimer = (ushort)IF->RetransTimer;
|
|
}
|
|
break;
|
|
|
|
case ND_STATE_PROBE:
|
|
//
|
|
// Retransmission timer expired. Check to see if
|
|
// sending another solicit would exceed the maximum.
|
|
//
|
|
if (NCE->NSCount >= NCE->NSLimit) {
|
|
//
|
|
// Failed to initiate connectivity to neighbor.
|
|
// Reset to dormant INCOMPLETE state.
|
|
//
|
|
NCE->NDState = ND_STATE_INCOMPLETE;
|
|
NCE->NSCount = 0;
|
|
|
|
//
|
|
// This neighbor is not reachable.
|
|
// IsUnreachable may already be TRUE.
|
|
// But we need to give FindNextHop an opportunity to round-robin.
|
|
//
|
|
NCE->IsUnreachable = TRUE;
|
|
NCE->LastReachability = IPv6TickCount; // Timestamp it.
|
|
NCE->DoRoundRobin = TRUE;
|
|
InvalidateRouteCache();
|
|
}
|
|
else {
|
|
//
|
|
// Retransmit probe solicitation. We can not call
|
|
// NeighborSolicitSend directly because we have
|
|
// the neighbor cache locked, so punt to a worker thread.
|
|
//
|
|
NCE->NSCount++;
|
|
NeighborCacheEntrySendSolicitHelper(NCE, pInfoList);
|
|
|
|
//
|
|
// Re-arm timer for the next solicitation.
|
|
//
|
|
NCE->NSTimer = (ushort)IF->RetransTimer;
|
|
}
|
|
|
|
// Fall-through to check for a waiting packet.
|
|
|
|
default:
|
|
//
|
|
// In rare cases (eg, see NeighborCacheInitialize)
|
|
// we can have a waiting packet when the state is not INCOMPLETE.
|
|
//
|
|
if (NCE->WaitQueue != NULL) {
|
|
LARGE_INTEGER Immediately;
|
|
|
|
Packet = NCE->WaitQueue;
|
|
NCE->WaitQueue = NULL;
|
|
|
|
//
|
|
// We use IPv6SendLater because we hold the neighbor cache lock.
|
|
//
|
|
Immediately.QuadPart = 0;
|
|
Status = IPv6SendLater(Immediately, // Send asap.
|
|
IF, Packet, PC(Packet)->pc_offset,
|
|
NCE->LinkAddress);
|
|
if (Status != NDIS_STATUS_SUCCESS) {
|
|
//
|
|
// We can't complete the packet here,
|
|
// because we hold the neighbor cache lock.
|
|
// So let NeighborCacheTimeout complete it.
|
|
//
|
|
PC(Packet)->pc_drop = TRUE;
|
|
PC(Packet)->pc_link = IF->PacketList;
|
|
IF->PacketList = Packet;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//* NeighborCacheTimeout
|
|
//
|
|
// Called periodically from IPv6Timeout/InterfaceTimeout
|
|
// to handle timeouts in the interface's neighbor cache.
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
// Note that NeighborCacheTimeout performs an unbounded
|
|
// (more precisely - bounded by the size of the cache)
|
|
// amount of work while holding the neighbor cache lock.
|
|
// (It does not however send packets.)
|
|
//
|
|
// One possible strategy that would help, if this is a problem,
|
|
// would be to have a second singly-linked list of NCEs
|
|
// that require action. With one traversal we reference NCEs
|
|
// and create the action list. Then we could traverse the action
|
|
// list at our leisure, taking/dropping the neighbor cache lock.
|
|
//
|
|
// On the other hand, this is all moot on a uniprocessor
|
|
// because our locks are spinlocks and we are already at DPC level.
|
|
// That is, on a uniprocessor KeAcquireSpinLockAtDpcLevel is a no-op.
|
|
//
|
|
void
|
|
NeighborCacheTimeout(Interface *IF)
|
|
{
|
|
NeighborCacheEntrySolicitInfo *InfoList = NULL;
|
|
NeighborCacheEntrySolicitInfo *Info;
|
|
NDIS_PACKET *PacketList;
|
|
NeighborCacheEntry *NCE;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->LockNC);
|
|
for (NCE = IF->FirstNCE; NCE != SentinelNCE(IF); NCE = NCE->Next) {
|
|
#if DBG
|
|
//
|
|
// If there is packet waiting, we must be doing something.
|
|
//
|
|
ASSERT((NCE->WaitQueue == NULL) || (NCE->NSTimer != 0));
|
|
|
|
//
|
|
// If we are sending solicitations, we must have a timer running.
|
|
//
|
|
ASSERT((NCE->NSCount == 0) || (NCE->NSTimer != 0));
|
|
|
|
//
|
|
// If the neighbor is unreachable, the interface must support ND or
|
|
// the neighbor must be in the INCOMPLETE state.
|
|
//
|
|
ASSERT(! NCE->IsUnreachable ||
|
|
((IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) ||
|
|
(NCE->NDState == ND_STATE_INCOMPLETE)));
|
|
|
|
switch (NCE->NDState) {
|
|
case ND_STATE_INCOMPLETE:
|
|
//
|
|
// In the INCOMPLETE state, we can either be passive
|
|
// (no timer running, not sending solicitations)
|
|
// or active (timer running, sending solicitations).
|
|
//
|
|
ASSERT((NCE->NSTimer == 0) ||
|
|
((NCE->NSLimit == MAX_MULTICAST_SOLICIT) ||
|
|
(NCE->NSLimit == MAX_UNREACH_SOLICIT)));
|
|
break;
|
|
|
|
case ND_STATE_PROBE:
|
|
//
|
|
// In the PROBE state, we are actively sending solicitations.
|
|
//
|
|
ASSERT((NCE->NSTimer != 0) &&
|
|
((NCE->NSLimit == MAX_UNICAST_SOLICIT) ||
|
|
(NCE->NSLimit == MAX_UNREACH_SOLICIT)));
|
|
break;
|
|
|
|
case ND_STATE_REACHABLE:
|
|
case ND_STATE_PERMANENT:
|
|
//
|
|
// In the REACHABLE and PERMANENT states.
|
|
// the neighbor can not be considered unreachable.
|
|
//
|
|
ASSERT(! NCE->IsUnreachable);
|
|
// fall-through
|
|
|
|
case ND_STATE_STALE:
|
|
//
|
|
// In the STALE, REACHABLE, and PERMANENT states,
|
|
// we are not sending solicitations and there is no timer running,
|
|
// unless there is a packet waiting.
|
|
//
|
|
ASSERT((NCE->NSCount == 0) &&
|
|
((NCE->NSTimer == 0) ||
|
|
((NCE->WaitQueue != NULL) && (NCE->NSTimer == 1))));
|
|
break;
|
|
|
|
default:
|
|
ABORTMSG("bad ND state");
|
|
}
|
|
#endif // DBG
|
|
|
|
if (NCE->NSTimer != 0) {
|
|
//
|
|
// Timer is running. Decrement and check for expiration.
|
|
//
|
|
if (--NCE->NSTimer == 0) {
|
|
//
|
|
// Timer went off. NeighborCacheEntryTimeout may add
|
|
// items to our packet list and info list.
|
|
//
|
|
NeighborCacheEntryTimeout(NCE, &InfoList);
|
|
}
|
|
}
|
|
}
|
|
|
|
PacketList = IF->PacketList;
|
|
IF->PacketList = NULL;
|
|
KeReleaseSpinLockFromDpcLevel(&IF->LockNC);
|
|
|
|
//
|
|
// Now that we've unlocked, send neighbor solicitations.
|
|
//
|
|
while ((Info = InfoList) != NULL) {
|
|
InfoList = Info->Next;
|
|
NeighborSolicitSend(Info->NCE, Info->DiscoveryAddress);
|
|
ReleaseNCE(Info->NCE);
|
|
ExFreePool(Info);
|
|
}
|
|
|
|
//
|
|
// And complete the packets that we will not be sending.
|
|
//
|
|
NeighborCacheCompletePackets(IF, PacketList);
|
|
}
|
|
|
|
|
|
//* NeighborCacheFlush
|
|
//
|
|
// Flushes unused neighbor cache entries.
|
|
// If an address is supplied, flushes the NCE (at most one) for that address.
|
|
// Otherwise, flushes all unused NCEs on the interface.
|
|
//
|
|
// May be called with the interface lock held.
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
NeighborCacheFlush(Interface *IF, const IPv6Addr *Addr)
|
|
{
|
|
NeighborCacheEntry *Delete = NULL;
|
|
NeighborCacheEntry *NCE, *NextNCE;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IF->LockNC, &OldIrql);
|
|
for (NCE = IF->FirstNCE; NCE != SentinelNCE(IF); NCE = NextNCE) {
|
|
|
|
NextNCE = NCE->Next;
|
|
if (Addr == NULL)
|
|
; // Examine this NCE then keep looking.
|
|
else if (IP6_ADDR_EQUAL(Addr, &NCE->NeighborAddress))
|
|
NextNCE = SentinelNCE(IF); // Can terminate loop after this NCE.
|
|
else
|
|
continue; // Skip this NCE.
|
|
|
|
//
|
|
// Can we flush this NCE?
|
|
//
|
|
if ((NCE->RefCnt == 0) &&
|
|
(NCE->WaitQueue == NULL)) {
|
|
//
|
|
// Just need to unlink it.
|
|
//
|
|
NCE->Next->Prev = NCE->Prev;
|
|
NCE->Prev->Next = NCE->Next;
|
|
InterlockedDecrement((long *)&IF->NCENumUnused);
|
|
|
|
//
|
|
// And put it on our Delete list.
|
|
//
|
|
NCE->Next = Delete;
|
|
Delete = NCE;
|
|
}
|
|
else {
|
|
if (NCE->NDState != ND_STATE_PERMANENT) {
|
|
//
|
|
// Forget everything that we know about this NCE.
|
|
//
|
|
NeighborCacheInitialize(IF, NCE, ND_STATE_INCOMPLETE, NULL);
|
|
}
|
|
}
|
|
}
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
|
|
//
|
|
// We may have changed state that affects routing.
|
|
//
|
|
InvalidateRouteCache();
|
|
|
|
//
|
|
// Finish by actually deleting the flushed NCEs.
|
|
//
|
|
while (Delete != NULL) {
|
|
NCE = Delete;
|
|
Delete = NCE->Next;
|
|
ExFreePool(NCE);
|
|
}
|
|
}
|
|
|
|
|
|
//* RouterSolicitReceive - Handle Router Solicitation messages.
|
|
//
|
|
// See section 6.2.6 of the ND spec.
|
|
//
|
|
void
|
|
RouterSolicitReceive(
|
|
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
|
|
ICMPv6Header UNALIGNED *ICMP) // ICMP header.
|
|
{
|
|
Interface *IF = Packet->NTEorIF->IF;
|
|
const void *SourceLinkAddress;
|
|
|
|
//
|
|
// Ignore the solicitation unless this is an advertising interface.
|
|
//
|
|
if (!(IF->Flags & IF_FLAG_ADVERTISES))
|
|
return;
|
|
|
|
//
|
|
// Validate the solicitation.
|
|
// By the time we get here, any IPv6 Authentication Header will have
|
|
// already been checked, as will have the ICMPv6 checksum. Still need
|
|
// to check the length, source address, hop limit, and ICMP code.
|
|
//
|
|
if ((Packet->IP->HopLimit != 255) ||
|
|
(Packet->Flags & PACKET_TUNNELED)) {
|
|
//
|
|
// Packet was forwarded by a router, therefore it cannot be
|
|
// from a legitimate neighbor. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received a routed router solicitation\n"));
|
|
return;
|
|
}
|
|
if (ICMP->Code != 0) {
|
|
//
|
|
// Bogus/corrupted router solicitation. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received a corrupted router solicitation\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We should have a 4-byte reserved field.
|
|
//
|
|
if (Packet->TotalSize < 4) {
|
|
//
|
|
// Packet too short to contain minimal solicitation.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received a too short solicitation\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The code below assumes a contiguous buffer for all the options
|
|
// (the remainder of the packet). If that isn't currently the
|
|
// case, do a pullup for the whole thing.
|
|
//
|
|
if (! PacketPullup(Packet, Packet->TotalSize, 1, 0)) {
|
|
// Can only fail if we run out of memory.
|
|
return;
|
|
}
|
|
|
|
ASSERT(Packet->ContigSize == Packet->TotalSize);
|
|
|
|
//
|
|
// Skip over 4 byte "Reserved" field, ignoring whatever may be in it.
|
|
//
|
|
AdjustPacketParams(Packet, 4);
|
|
|
|
//
|
|
// We may have a source link-layer address option present.
|
|
// Check for it and silently ignore all others.
|
|
//
|
|
SourceLinkAddress = NULL;
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength;
|
|
|
|
//
|
|
// Validate the option length.
|
|
//
|
|
if ((Packet->ContigSize < 8) ||
|
|
((OptionLength = *((uchar *)Packet->Data + 1) << 3) == 0) ||
|
|
(OptionLength > Packet->ContigSize)) {
|
|
//
|
|
// Invalid option length. We MUST silently drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received option with bogus length\n"));
|
|
return;
|
|
}
|
|
|
|
if (*((uchar *)Packet->Data) == ND_OPTION_SOURCE_LINK_LAYER_ADDRESS) {
|
|
//
|
|
// Some interfaces do not use SLLA and TLLA options.
|
|
// For example, see RFC 2893 section 3.8.
|
|
//
|
|
if (IF->ReadLLOpt != NULL) {
|
|
//
|
|
// Parse the link-layer address option.
|
|
//
|
|
SourceLinkAddress = (*IF->ReadLLOpt)(IF->LinkContext,
|
|
(uchar *)Packet->Data);
|
|
if (SourceLinkAddress == NULL) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received bogus ll option\n"));
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// Note that if there are multiple options for some bogus reason,
|
|
// we use the last one. We must sanity-check all option lengths.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
//
|
|
AdjustPacketParams(Packet, OptionLength);
|
|
}
|
|
|
|
//
|
|
// We've received and parsed a valid router solicitation.
|
|
//
|
|
|
|
if (IsUnspecified(AlignAddr(&Packet->IP->Source))) {
|
|
//
|
|
// This is a new check, introduced post-RFC 1970.
|
|
//
|
|
if (SourceLinkAddress != NULL) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterSolicitReceive: "
|
|
"Received SLA with unspecified Source?\n"));
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Only bother with this if SourceLinkAddress is present;
|
|
// if it's not, NeighborCacheUpdate won't do anything.
|
|
//
|
|
if (SourceLinkAddress != NULL) {
|
|
NeighborCacheEntry *NCE;
|
|
//
|
|
// Get the Neighbor Cache Entry for the source of this RS.
|
|
//
|
|
NCE = FindOrCreateNeighbor(IF, AlignAddr(&Packet->IP->Source));
|
|
if (NCE != NULL) {
|
|
//
|
|
// Update the Neighbor Cache Entry for the source of this RS.
|
|
//
|
|
// REVIEW: We deviate from the spec here. The spec says
|
|
// that if you receive an RS from a Source, then you MUST
|
|
// set the IsRouter flag for that Source to FALSE.
|
|
// However consider a node which is not advertising
|
|
// but it is forwarding. Such a node might send an RS
|
|
// but IsRouter should be TRUE for that node.
|
|
//
|
|
NeighborCacheUpdate(NCE, SourceLinkAddress, FALSE);
|
|
ReleaseNCE(NCE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(IF->Flags & IF_FLAG_MULTICAST)) {
|
|
NetTableEntry *NTE;
|
|
int GotSource;
|
|
IPv6Addr Source;
|
|
|
|
//
|
|
// What source address should we use for the RA?
|
|
//
|
|
if (IsNTE(Packet->NTEorIF) &&
|
|
((NTE = CastToNTE(Packet->NTEorIF))->Scope == ADE_LINK_LOCAL)) {
|
|
//
|
|
// The RS was received on a link-local NTE, so use that address.
|
|
// IPv6HeaderReceive checks that the NTE is valid.
|
|
//
|
|
Source = NTE->Address;
|
|
GotSource = TRUE;
|
|
}
|
|
else {
|
|
//
|
|
// Try to get a valid link-local address.
|
|
//
|
|
GotSource = GetLinkLocalAddress(IF, &Source);
|
|
}
|
|
|
|
if (GotSource) {
|
|
//
|
|
// The interface doesn't support multicast, so instead,
|
|
// immediately send a unicast reply. This allows router
|
|
// discovery to work on NBMA interfaces such as for ISATAP.
|
|
//
|
|
RouterAdvertSend(IF, &Source, AlignAddr(&Packet->IP->Source));
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Send a Router Advertisement very soon.
|
|
// The randomization in IPv6Timeout initialization
|
|
// provides the randomization required when sending
|
|
// a RA in response to an RS.
|
|
// NB: Although we checked IF_FLAG_ADVERTISES above,
|
|
// the situation could be different now.
|
|
//
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
if (IF->RATimer != 0) {
|
|
//
|
|
// If MAX_RA_DELAY_TIME is not 1, then should use
|
|
// RandomNumber to generate the number of ticks.
|
|
//
|
|
C_ASSERT(MAX_RA_DELAY_TIME == 1);
|
|
IF->RATimer = 1;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
}
|
|
}
|
|
|
|
|
|
//* RouterAdvertReceive - Handle Router Advertisement messages.
|
|
//
|
|
// Validate message, update Default Router list, On-Link Prefix list,
|
|
// perform address auto-configuration.
|
|
// See sections 6.1.2, 6.3.4 of RFC 2461.
|
|
//
|
|
void
|
|
RouterAdvertReceive(
|
|
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
|
|
ICMPv6Header UNALIGNED *ICMP) // ICMP header.
|
|
{
|
|
Interface *IF = Packet->NTEorIF->IF;
|
|
uint CurHopLimit, RouterLifetime, MinLifetime;
|
|
uchar Flags;
|
|
uint AdvReachableTime, RetransTimer;
|
|
const void *SourceLinkAddress;
|
|
NeighborCacheEntry *NCE;
|
|
NDRouterAdvertisement UNALIGNED *RA;
|
|
|
|
//
|
|
// Ignore the advertisement if this is an advertising interface.
|
|
//
|
|
if (IF->Flags & IF_FLAG_ADVERTISES)
|
|
return;
|
|
|
|
//
|
|
// Validate the advertisement.
|
|
// By the time we get here, any IPv6 Authentication Header will have
|
|
// already been checked, as will have the ICMPv6 checksum. Still need
|
|
// to check the length, source address, hop limit, and ICMP code.
|
|
//
|
|
if ((Packet->IP->HopLimit != 255) ||
|
|
(Packet->Flags & PACKET_TUNNELED)) {
|
|
//
|
|
// Packet was forwarded by a router, therefore it cannot be
|
|
// from a legitimate neighbor. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received a routed router advertisement\n"));
|
|
return;
|
|
}
|
|
if (ICMP->Code != 0) {
|
|
//
|
|
// Bogus/corrupted router advertisement. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received a corrupted router advertisement\n"));
|
|
return;
|
|
}
|
|
if (!IsLinkLocal(AlignAddr(&Packet->IP->Source))) {
|
|
//
|
|
// Source address should always be link-local. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Non-link-local source in router advertisement\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Pull CurHopLimit, Flags, RouterLifetime,
|
|
// AdvReachableTime, RetransTimer out of the packet.
|
|
//
|
|
if (! PacketPullup(Packet, sizeof *RA, 1, 0)) {
|
|
if (Packet->TotalSize < sizeof *RA) {
|
|
//
|
|
// Packet too short to contain minimal RA.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received a too short router advertisement\n"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
RA = (NDRouterAdvertisement UNALIGNED *) Packet->Data;
|
|
CurHopLimit = RA->CurHopLimit;
|
|
Flags = RA->Flags;
|
|
RouterLifetime = net_short(RA->RouterLifetime);
|
|
AdvReachableTime = net_long(RA->ReachableTime);
|
|
RetransTimer = net_long(RA->RetransTimer);
|
|
AdjustPacketParams(Packet, sizeof *RA);
|
|
|
|
//
|
|
// The code below assumes a contiguous buffer for all the options
|
|
// (the remainder of the packet). If that isn't currently the
|
|
// case, do a pullup for the whole thing. We need alignment for
|
|
// IPv6Addr because of the options that we look at.
|
|
//
|
|
if (! PacketPullup(Packet, Packet->TotalSize,
|
|
__builtin_alignof(IPv6Addr), 0)) {
|
|
// Can only fail if we run out of memory.
|
|
return;
|
|
}
|
|
|
|
ASSERT(Packet->ContigSize == Packet->TotalSize);
|
|
|
|
//
|
|
// Look for a source link-layer address option.
|
|
// Also sanity-check the options before doing anything permanent.
|
|
//
|
|
SourceLinkAddress = NULL;
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength;
|
|
|
|
//
|
|
// Validate the option length.
|
|
//
|
|
if ((Packet->ContigSize < 8) ||
|
|
((OptionLength = *((uchar *)Packet->Data + 1) << 3) == 0) ||
|
|
(OptionLength > Packet->ContigSize)) {
|
|
//
|
|
// Invalid option length. We MUST silently drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received RA option with with bogus length\n"));
|
|
return;
|
|
}
|
|
|
|
switch (*(uchar *)Packet->Data) {
|
|
case ND_OPTION_SOURCE_LINK_LAYER_ADDRESS:
|
|
//
|
|
// Some interfaces do not use SLLA and TLLA options.
|
|
// For example, see RFC 2893 section 3.8.
|
|
//
|
|
if (IF->ReadLLOpt != NULL) {
|
|
//
|
|
// Parse the link-layer address option.
|
|
//
|
|
SourceLinkAddress = (*IF->ReadLLOpt)(IF->LinkContext,
|
|
(uchar *)Packet->Data);
|
|
if (SourceLinkAddress == NULL) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received RA with bogus ll option\n"));
|
|
return;
|
|
}
|
|
//
|
|
// Note that if there are multiple options for some bogus
|
|
// reason, we use the last one. We sanity-check all the
|
|
// options.
|
|
//
|
|
}
|
|
break;
|
|
|
|
case ND_OPTION_MTU:
|
|
//
|
|
// Sanity-check the option.
|
|
//
|
|
if (OptionLength != 8) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received RA with bogus mtu option\n"));
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case ND_OPTION_PREFIX_INFORMATION: {
|
|
NDOptionPrefixInformation UNALIGNED *option =
|
|
(NDOptionPrefixInformation *)Packet->Data;
|
|
|
|
//
|
|
// Sanity-check the option.
|
|
//
|
|
if ((OptionLength != 32) ||
|
|
(option->PrefixLength > IPV6_ADDRESS_LENGTH)) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received RA with bogus prefix option\n"));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ND_OPTION_ROUTE_INFORMATION: {
|
|
NDOptionRouteInformation UNALIGNED *option =
|
|
(NDOptionRouteInformation *)Packet->Data;
|
|
|
|
//
|
|
// Sanity-check the option.
|
|
// Depending on PrefixLength, the option may be 8, 16, 24 bytes.
|
|
// At this point, we know it is a multiple of 8 greater than zero.
|
|
//
|
|
if ((OptionLength > 24) ||
|
|
(option->PrefixLength > IPV6_ADDRESS_LENGTH) ||
|
|
((option->PrefixLength > 64) && (OptionLength < 24)) ||
|
|
((option->PrefixLength > 0) && (OptionLength < 16))) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Received RA with bogus route option\n"));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
// Note that we are not updating TotalSize here,
|
|
// so we can use it below to back up.
|
|
//
|
|
(uchar *)Packet->Data += OptionLength;
|
|
Packet->ContigSize -= OptionLength;
|
|
}
|
|
|
|
//
|
|
// Reset Data pointer & ContigSize.
|
|
//
|
|
(uchar *)Packet->Data -= Packet->TotalSize;
|
|
Packet->ContigSize += Packet->TotalSize;
|
|
|
|
//
|
|
// Get the Neighbor Cache Entry for the source of this RA.
|
|
//
|
|
NCE = FindOrCreateNeighbor(IF, AlignAddr(&Packet->IP->Source));
|
|
if (NCE == NULL) {
|
|
//
|
|
// Couldn't find or create NCE. Drop the packet.
|
|
//
|
|
return;
|
|
}
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
|
|
//
|
|
// Cache the parity of the "other stateful config" flag.
|
|
//
|
|
if (Flags & ND_RA_FLAG_OTHER) {
|
|
IF->Flags |= IF_FLAG_OTHER_STATEFUL_CONFIG;
|
|
} else {
|
|
IF->Flags &= ~IF_FLAG_OTHER_STATEFUL_CONFIG;
|
|
}
|
|
|
|
//
|
|
// If we just reconnected this interface,
|
|
// then give all state learned from auto-configuration
|
|
// a small "accelerated" lifetime.
|
|
// The processing below might extend accelerated lifetimes.
|
|
//
|
|
if (IF->Flags & IF_FLAG_MEDIA_RECONNECTED) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE,
|
|
"RouterAdvertReceive(IF %p) - reconnecting\n", IF));
|
|
|
|
IF->Flags &= ~IF_FLAG_MEDIA_RECONNECTED;
|
|
|
|
//
|
|
// Reset auto-configured address lifetimes.
|
|
//
|
|
AddrConfResetAutoConfig(IF,
|
|
2 * MAX_RA_DELAY_TIME + MIN_DELAY_BETWEEN_RAS);
|
|
|
|
//
|
|
// Similarly, reset auto-configured routes.
|
|
//
|
|
RouteTableResetAutoConfig(IF,
|
|
2 * MAX_RA_DELAY_TIME + MIN_DELAY_BETWEEN_RAS);
|
|
|
|
//
|
|
// Reset parameters that are learned from RAs.
|
|
//
|
|
InterfaceResetAutoConfig(IF);
|
|
}
|
|
|
|
//
|
|
// A host MUST stop sending router solicitations for an interface upon
|
|
// receiving a valid router advertisement with a non-zero router lifetime.
|
|
// We go a step further and, on a non-multicast interface, stop after the
|
|
// first valid response - presumably that's all we are going to receive.
|
|
// Note that we should always send at least one router solicitation,
|
|
// even if we receive an unsolicited router advertisement first.
|
|
//
|
|
if ((RouterLifetime != 0) || !(IF->Flags & IF_FLAG_MULTICAST)) {
|
|
if (IF->RSCount > 0)
|
|
IF->RSTimer = 0;
|
|
}
|
|
|
|
//
|
|
// Update the BaseReachableTime and ReachableTime.
|
|
// NB: We use a lock for coordinated updates, but other code
|
|
// reads the ReachableTime field without a lock.
|
|
//
|
|
if ((AdvReachableTime != 0) &&
|
|
(AdvReachableTime != IF->BaseReachableTime)) {
|
|
|
|
IF->BaseReachableTime = AdvReachableTime;
|
|
IF->ReachableTime = CalcReachableTime(AdvReachableTime);
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
|
|
//
|
|
// Update the Neighbor Cache Entry for the source of this RA.
|
|
//
|
|
NeighborCacheUpdate(NCE, SourceLinkAddress, TRUE);
|
|
|
|
//
|
|
// Update the Default Router List.
|
|
// Note that router lifetimes on the wire,
|
|
// unlike prefix lifetimes, can not be infinite.
|
|
//
|
|
ASSERT(RouterLifetime != INFINITE_LIFETIME); // Because it's 16 bits.
|
|
MinLifetime = RouterLifetime = ConvertSecondsToTicks(RouterLifetime);
|
|
|
|
RouteTableUpdate(NULL, // System update.
|
|
IF, NCE,
|
|
&UnspecifiedAddr, 0, 0,
|
|
RouterLifetime, RouterLifetime,
|
|
ExtractRoutePreference(Flags),
|
|
RTE_TYPE_AUTOCONF,
|
|
FALSE, FALSE);
|
|
|
|
//
|
|
// Update the hop limit for the interface.
|
|
// NB: We rely on loads/stores of the CurHopLimit field being atomic.
|
|
//
|
|
if (CurHopLimit != 0) {
|
|
IF->CurHopLimit = CurHopLimit;
|
|
}
|
|
|
|
//
|
|
// Update the RetransTimer field.
|
|
// NB: We rely on loads/stores of this field being atomic.
|
|
//
|
|
if (RetransTimer != 0)
|
|
IF->RetransTimer = ConvertMillisToTicks(RetransTimer);
|
|
|
|
//
|
|
// Process any LinkMTU, PrefixInformation options.
|
|
//
|
|
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength;
|
|
|
|
//
|
|
// The option length was validated in the first pass
|
|
// over the options, above.
|
|
//
|
|
OptionLength = *((uchar *)Packet->Data + 1) << 3;
|
|
|
|
switch (*(uchar *)Packet->Data) {
|
|
case ND_OPTION_MTU: {
|
|
NDOptionMTU UNALIGNED *option =
|
|
(NDOptionMTU UNALIGNED *)Packet->Data;
|
|
uint LinkMTU = net_long(option->MTU);
|
|
|
|
if ((IPv6_MINIMUM_MTU <= LinkMTU) &&
|
|
(LinkMTU <= IF->TrueLinkMTU))
|
|
UpdateLinkMTU(IF, LinkMTU);
|
|
break;
|
|
}
|
|
|
|
case ND_OPTION_PREFIX_INFORMATION: {
|
|
NDOptionPrefixInformation UNALIGNED *option =
|
|
(NDOptionPrefixInformation UNALIGNED *)Packet->Data;
|
|
uint PrefixLength, ValidLifetime, PreferredLifetime;
|
|
IPv6Addr Prefix;
|
|
|
|
//
|
|
// Extract the prefix length and prefix from option. We
|
|
// MUST ignore any bits in the prefix after the prefix length.
|
|
// RouteTableUpdate and SitePrefixUpdate do that also,
|
|
// but we look at the prefix directly here.
|
|
//
|
|
PrefixLength = option->PrefixLength; // In bits.
|
|
CopyPrefix(&Prefix, AlignAddr(&option->Prefix), PrefixLength);
|
|
|
|
ValidLifetime = net_long(option->ValidLifetime);
|
|
ValidLifetime = ConvertSecondsToTicks(ValidLifetime);
|
|
PreferredLifetime = net_long(option->PreferredLifetime);
|
|
PreferredLifetime = ConvertSecondsToTicks(PreferredLifetime);
|
|
if (MinLifetime > PreferredLifetime)
|
|
MinLifetime = PreferredLifetime;
|
|
|
|
//
|
|
// Silently ignore link-local and multicast prefixes.
|
|
// REVIEW - Is this actually the required check?
|
|
//
|
|
if (IsLinkLocal(&Prefix) || IsMulticast(&Prefix))
|
|
break;
|
|
|
|
//
|
|
// Generally at least one flag bit is set,
|
|
// but we must process them independently.
|
|
//
|
|
|
|
if (option->Flags & ND_PREFIX_FLAG_ON_LINK)
|
|
RouteTableUpdate(NULL, // System update.
|
|
IF, NULL,
|
|
&Prefix, PrefixLength, 0,
|
|
ValidLifetime, ValidLifetime,
|
|
ROUTE_PREF_ON_LINK,
|
|
RTE_TYPE_AUTOCONF,
|
|
FALSE, FALSE);
|
|
|
|
if (option->Flags & ND_PREFIX_FLAG_ROUTE)
|
|
RouteTableUpdate(NULL, // System update.
|
|
IF, NCE,
|
|
&Prefix, PrefixLength, 0,
|
|
ValidLifetime, ValidLifetime,
|
|
ROUTE_PREF_MEDIUM,
|
|
RTE_TYPE_AUTOCONF,
|
|
FALSE, FALSE);
|
|
|
|
//
|
|
// We ignore site-local prefixes here. Above check
|
|
// filters out link-local and multicast prefixes.
|
|
//
|
|
if (! IsSiteLocal(&Prefix)) {
|
|
uint SitePrefixLength;
|
|
|
|
//
|
|
// If the S bit is clear, then we check the A bit
|
|
// and use the interface's default site prefix length.
|
|
// This lets us infer site prefixes when routers
|
|
// do not support the S bit.
|
|
//
|
|
if (option->Flags & ND_PREFIX_FLAG_SITE_PREFIX)
|
|
SitePrefixLength = option->SitePrefixLength;
|
|
else if (option->Flags & ND_PREFIX_FLAG_AUTONOMOUS)
|
|
SitePrefixLength = IF->DefSitePrefixLength;
|
|
else
|
|
SitePrefixLength = 0;
|
|
|
|
//
|
|
// Ignore if the Site Prefix Length is zero.
|
|
//
|
|
if (SitePrefixLength != 0)
|
|
SitePrefixUpdate(IF,
|
|
&Prefix, SitePrefixLength,
|
|
ValidLifetime);
|
|
}
|
|
|
|
if (option->Flags & ND_PREFIX_FLAG_AUTONOMOUS) {
|
|
//
|
|
// Attempt autonomous address-configuration.
|
|
//
|
|
if (PreferredLifetime > ValidLifetime) {
|
|
//
|
|
// MAY log a system management error.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: "
|
|
"Bogus valid/preferred lifetimes\n"));
|
|
}
|
|
else if ((PrefixLength + IPV6_ID_LENGTH) !=
|
|
IPV6_ADDRESS_LENGTH) {
|
|
//
|
|
// If the sum of the prefix length and the interface
|
|
// identifier (always 64 bits in our implementation)
|
|
// is not 128 bits, we MUST ignore the prefix option.
|
|
// MAY log a system management error.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RouterAdvertReceive: Got prefix length of %u, "
|
|
"must be 64 for auto-config\n", PrefixLength));
|
|
}
|
|
else if (IF->CreateToken != NULL) {
|
|
AddrConfUpdate(IF, &Prefix,
|
|
ValidLifetime, PreferredLifetime,
|
|
FALSE, // Not authenticated.
|
|
NULL); // Do not return NTE.
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ND_OPTION_ROUTE_INFORMATION: {
|
|
NDOptionRouteInformation UNALIGNED *option =
|
|
(NDOptionRouteInformation UNALIGNED *)Packet->Data;
|
|
uint PrefixLength, RouteLifetime;
|
|
IPv6Addr *Prefix;
|
|
|
|
//
|
|
// Extract the prefix length and prefix from option. We
|
|
// MUST ignore any bits in the prefix after the prefix length.
|
|
// RouteTableUpdate does that for us.
|
|
//
|
|
PrefixLength = option->PrefixLength; // In bits.
|
|
Prefix = AlignAddr(&option->Prefix);
|
|
|
|
RouteLifetime = net_long(option->RouteLifetime);
|
|
RouteLifetime = ConvertSecondsToTicks(RouteLifetime);
|
|
if (MinLifetime > RouteLifetime)
|
|
MinLifetime = RouteLifetime;
|
|
|
|
RouteTableUpdate(NULL, // System update.
|
|
IF, NCE,
|
|
Prefix, PrefixLength, 0,
|
|
RouteLifetime, RouteLifetime,
|
|
ExtractRoutePreference(option->Flags),
|
|
RTE_TYPE_AUTOCONF,
|
|
FALSE, FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
//
|
|
AdjustPacketParams(Packet, OptionLength);
|
|
}
|
|
|
|
if (!(IF->Flags & IF_FLAG_MULTICAST) && (IF->RSTimer == 0)) {
|
|
//
|
|
// On non-multicast interfaces such as the ISATAP interface,
|
|
// we need to send periodic Router Solicitations. We want
|
|
// to do so as infrequently as possible and still be reasonably
|
|
// robust. We'll try to refresh it halfway through the lowest
|
|
// lifetime in the RA we saw. However, if a renumbering event
|
|
// is going on, and a lifetime is low, we don't want to send
|
|
// too often, so we put on a minimum cap equal to what we'd
|
|
// use if we never got an RA.
|
|
//
|
|
if (MinLifetime < SLOW_RTR_SOLICITATION_INTERVAL * 2)
|
|
IF->RSTimer = SLOW_RTR_SOLICITATION_INTERVAL;
|
|
else
|
|
IF->RSTimer = MinLifetime / 2;
|
|
IF->RSCount = MAX_RTR_SOLICITATIONS;
|
|
}
|
|
|
|
//
|
|
// Done with packet.
|
|
//
|
|
ReleaseNCE(NCE);
|
|
}
|
|
|
|
|
|
//* NeighborSolicitReceive - Handle Neighbor Solicitation messages.
|
|
//
|
|
// Validate message, update ND cache, and reply with Neighbor Advertisement.
|
|
// See section 7.2.4 of RFC 1970.
|
|
//
|
|
void
|
|
NeighborSolicitReceive(
|
|
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
|
|
ICMPv6Header UNALIGNED *ICMP) // ICMP header.
|
|
{
|
|
Interface *IF = Packet->NTEorIF->IF;
|
|
const IPv6Addr *TargetAddress;
|
|
const void *SourceLinkAddress;
|
|
NDIS_STATUS Status;
|
|
NDIS_PACKET *AdvertPacket;
|
|
uint Offset;
|
|
uint PayloadLength;
|
|
void *Mem;
|
|
uint MemLen;
|
|
IPv6Header UNALIGNED *AdvertIP;
|
|
ICMPv6Header UNALIGNED *AdvertICMP;
|
|
ulong Flags;
|
|
void *AdvertTargetOption;
|
|
IPv6Addr *AdvertTargetAddress;
|
|
void *DestLinkAddress;
|
|
NetTableEntryOrInterface *TargetNTEorIF;
|
|
ushort TargetType;
|
|
|
|
//
|
|
// Validate the solicitation.
|
|
// By the time we get here, any IPv6 Authentication Header will have
|
|
// already been checked, as will have the ICMPv6 checksum. Still need
|
|
// to check the IP Hop Limit, and the ICMP code and length.
|
|
//
|
|
if ((Packet->IP->HopLimit != 255) ||
|
|
(Packet->Flags & PACKET_TUNNELED)) {
|
|
//
|
|
// Packet was forwarded by a router, therefore it cannot be
|
|
// from a legitimate neighbor. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Received a routed neighbor solicitation\n"));
|
|
return;
|
|
}
|
|
if (ICMP->Code != 0) {
|
|
//
|
|
// Bogus/corrupted neighbor solicitation message. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Received a corrupted neighbor solicitation\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Next we have a 4 byte reserved field, then an IPv6 address.
|
|
//
|
|
if (! PacketPullup(Packet, 4 + sizeof(IPv6Addr),
|
|
__builtin_alignof(IPv6Addr), 0)) {
|
|
if (Packet->TotalSize < 4 + sizeof(IPv6Addr)) {
|
|
//
|
|
// Packet too short to contain minimal solicitation.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Received a too short solicitation\n"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Skip over 4 byte "Reserved" field, ignoring whatever may be in it.
|
|
// Get a pointer to the target address which follows, then skip over that.
|
|
//
|
|
TargetAddress = AlignAddr((IPv6Addr UNALIGNED *)
|
|
((uchar *)Packet->Data + 4));
|
|
AdjustPacketParams(Packet, 4 + sizeof(IPv6Addr));
|
|
|
|
//
|
|
// See if we're the target of the solicitation. If the target address is
|
|
// not a unicast or anycast address assigned to receiving interface,
|
|
// then we must silently drop the packet.
|
|
//
|
|
TargetNTEorIF = FindAddressOnInterface(IF, TargetAddress, &TargetType);
|
|
if (TargetNTEorIF == NULL) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"NeighborSolicitReceive: disabled IF\n"));
|
|
return;
|
|
}
|
|
else if (TargetType == ADE_NONE) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"NeighborSolicitReceive: Did not find target address\n"));
|
|
goto Return;
|
|
}
|
|
else if (TargetType == ADE_MULTICAST) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Target address not uni/anycast\n"));
|
|
goto Return;
|
|
}
|
|
|
|
ASSERT(((TargetType == ADE_UNICAST) &&
|
|
IsNTE(TargetNTEorIF) &&
|
|
IP6_ADDR_EQUAL(&CastToNTE(TargetNTEorIF)->Address,
|
|
TargetAddress)) ||
|
|
(TargetType == ADE_ANYCAST));
|
|
|
|
//
|
|
// The code below assumes a contiguous buffer for all the options
|
|
// (the remainder of the packet). If that isn't currently the
|
|
// case, do a pullup for the whole thing.
|
|
//
|
|
if (! PacketPullup(Packet, Packet->TotalSize, 1, 0)) {
|
|
// Can only fail if we run out of memory.
|
|
goto Return;
|
|
}
|
|
|
|
ASSERT(Packet->ContigSize == Packet->TotalSize);
|
|
|
|
//
|
|
// We may have a source link-layer address option present.
|
|
// Check for it and silently ignore all others.
|
|
//
|
|
SourceLinkAddress = NULL;
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength;
|
|
|
|
//
|
|
// Validate the option length.
|
|
//
|
|
if ((Packet->ContigSize < 8) ||
|
|
((OptionLength = *((uchar *)Packet->Data + 1) << 3) == 0) ||
|
|
(OptionLength > Packet->ContigSize)) {
|
|
//
|
|
// Invalid option length. We MUST silently drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Received option with bogus length\n"));
|
|
goto Return;
|
|
}
|
|
|
|
if (*((uchar *)Packet->Data) == ND_OPTION_SOURCE_LINK_LAYER_ADDRESS) {
|
|
//
|
|
// Some interfaces do not use SLLA and TLLA options.
|
|
// For example, see RFC 2893 section 3.8.
|
|
//
|
|
if (IF->ReadLLOpt != NULL) {
|
|
//
|
|
// Parse the link-layer address option.
|
|
//
|
|
SourceLinkAddress = (*IF->ReadLLOpt)(IF->LinkContext,
|
|
(uchar *)Packet->Data);
|
|
if (SourceLinkAddress == NULL) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: "
|
|
"Received bogus ll option\n"));
|
|
goto Return;
|
|
}
|
|
//
|
|
// Note that if there are multiple options for some bogus
|
|
// reason, we use the last one. We sanity-check all the
|
|
// options.
|
|
//
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
//
|
|
AdjustPacketParams(Packet, OptionLength);
|
|
}
|
|
|
|
if (IsUnspecified(AlignAddr(&Packet->IP->Source))) {
|
|
//
|
|
// Checks that were added post-RFC 1970.
|
|
//
|
|
|
|
if (!IsSolicitedNodeMulticast(AlignAddr(&Packet->IP->Dest))) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: NS with null src, bad dst\n"));
|
|
goto Return;
|
|
}
|
|
|
|
if (SourceLinkAddress != NULL) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborSolicitReceive: NS with null src and SLA\n"));
|
|
goto Return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We've received and parsed a valid neighbor solicitation.
|
|
//
|
|
// First, we check our Duplicate Address Detection state.
|
|
// (See RFC 2461 section 5.4.3.)
|
|
//
|
|
if (TargetType == ADE_UNICAST) {
|
|
NetTableEntry *TargetNTE = CastToNTE(TargetNTEorIF);
|
|
int DefendAddress = TRUE;
|
|
|
|
//
|
|
// As an optimization, we perform this check without holding a lock.
|
|
// This results in one less lock acquire/release for the common case.
|
|
//
|
|
if (!IsValidNTE(TargetNTE)) {
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
//
|
|
// If the source address of the solicitation is the unspecified
|
|
// address, it came from a node performing DAD on the address.
|
|
// If our address is tentative, then we make it duplicate.
|
|
//
|
|
if (IsUnspecified(AlignAddr(&Packet->IP->Source)) &&
|
|
IsTentativeNTE(TargetNTE)) {
|
|
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
|
|
"NeighborSolicitReceive: DAD found duplicate\n"));
|
|
AddrConfDuplicate(IF, TargetNTE);
|
|
}
|
|
|
|
if (!IsValidNTE(TargetNTE)) {
|
|
//
|
|
// Do not advertise/defend our invalid address.
|
|
//
|
|
DefendAddress = FALSE;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
}
|
|
|
|
if (! DefendAddress) {
|
|
//
|
|
// We should not respond to the solicit.
|
|
//
|
|
goto Return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the Neighbor Cache Entry for the source of this solicit.
|
|
// In this case, only bother if the SourceLinkAddress is present;
|
|
// if it's not, NeighborCacheUpdate won't do anything.
|
|
//
|
|
if (!IsUnspecified(AlignAddr(&Packet->IP->Source)) &&
|
|
(SourceLinkAddress != NULL)) {
|
|
NeighborCacheEntry *NCE;
|
|
|
|
NCE = FindOrCreateNeighbor(IF, AlignAddr(&Packet->IP->Source));
|
|
if (NCE != NULL) {
|
|
NeighborCacheUpdate(NCE, SourceLinkAddress, FALSE);
|
|
ReleaseNCE(NCE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send a solicited neighbor advertisement back to the source.
|
|
//
|
|
// Interface to send on is 'IF' (I/F solicitation received on).
|
|
//
|
|
ICMPv6OutStats.icmps_msgs++;
|
|
|
|
//
|
|
// Allocate a packet for advertisement.
|
|
// In addition to the 24 bytes for the solicit proper, leave space
|
|
// for target link-layer address option (round option length up to
|
|
// 8-byte multiple).
|
|
//
|
|
PayloadLength = 24;
|
|
if (IF->WriteLLOpt != NULL)
|
|
PayloadLength += (IF->LinkAddressLength + 2 + 7) & ~7;
|
|
Offset = IF->LinkHeaderSize;
|
|
MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
|
|
|
|
Status = IPv6AllocatePacket(MemLen, &AdvertPacket, &Mem);
|
|
if (Status != NDIS_STATUS_SUCCESS) {
|
|
ICMPv6OutStats.icmps_errors++;
|
|
goto Return;
|
|
}
|
|
|
|
//
|
|
// Prepare IP header of advertisement.
|
|
//
|
|
AdvertIP = (IPv6Header UNALIGNED *)((uchar *)Mem + Offset);
|
|
AdvertIP->VersClassFlow = IP_VERSION;
|
|
AdvertIP->PayloadLength = net_short((ushort)PayloadLength);
|
|
AdvertIP->NextHeader = IP_PROTOCOL_ICMPv6;
|
|
AdvertIP->HopLimit = 255;
|
|
|
|
//
|
|
// Prepare ICMP header.
|
|
//
|
|
AdvertICMP = (ICMPv6Header UNALIGNED *)(AdvertIP + 1);
|
|
AdvertICMP->Type = ICMPv6_NEIGHBOR_ADVERT;
|
|
AdvertICMP->Code = 0;
|
|
AdvertICMP->Checksum = 0;
|
|
Flags = 0;
|
|
if (IF->Flags & IF_FLAG_FORWARDS)
|
|
Flags |= ND_NA_FLAG_ROUTER;
|
|
|
|
//
|
|
// We prevent loopback of all ND packets.
|
|
//
|
|
PC(AdvertPacket)->Flags = NDIS_FLAGS_DONT_LOOPBACK;
|
|
|
|
//
|
|
// Check source of solicitation to see where we should direct our
|
|
// advertisement (and determine what type of advertisement to send).
|
|
//
|
|
if (IsUnspecified(AlignAddr(&Packet->IP->Source))) {
|
|
//
|
|
// Solicitation came from an unspecified address (presumably a node
|
|
// undergoing initialization), so we need to multicast our response.
|
|
// We also don't set the solicited flag since we can't specify the
|
|
// specific node our advertisement is directed at.
|
|
//
|
|
AdvertIP->Dest = AllNodesOnLinkAddr;
|
|
PC(AdvertPacket)->Flags |= NDIS_FLAGS_MULTICAST_PACKET;
|
|
|
|
DestLinkAddress = alloca(IF->LinkAddressLength);
|
|
(*IF->ConvertAddr)(IF->LinkContext, &AllNodesOnLinkAddr,
|
|
DestLinkAddress);
|
|
|
|
} else {
|
|
//
|
|
// We know who sent the solicitation, so we can respond by unicasting
|
|
// our solicited advertisement back to the soliciter.
|
|
//
|
|
AdvertIP->Dest = Packet->IP->Source;
|
|
Flags |= ND_NA_FLAG_SOLICITED;
|
|
|
|
//
|
|
// Find link-level address for above. We should have it cached (note
|
|
// that it will be cached if it just came in on this solicitation).
|
|
//
|
|
if (SourceLinkAddress != NULL) {
|
|
//
|
|
// This is faster than checking the ND cache and
|
|
// it should be the common case. In fact, the RFC
|
|
// requires that senders include the SLLA option,
|
|
// except for point-to-point interfaces.
|
|
//
|
|
DestLinkAddress = (void *)SourceLinkAddress;
|
|
} else if (IF->Flags & IF_FLAG_P2P) {
|
|
//
|
|
// Use the link-layer address of the other endpoint,
|
|
// which follows our link-layer address in memory.
|
|
//
|
|
DestLinkAddress = IF->LinkAddress + IF->LinkAddressLength;
|
|
} else {
|
|
//
|
|
// We shouldn't get here, because senders MUST
|
|
// include the SLLA option. But make a best effort
|
|
// to reply anyway.
|
|
//
|
|
DestLinkAddress = alloca(IF->LinkAddressLength);
|
|
|
|
if (!NeighborCacheSearch(IF, AlignAddr(&Packet->IP->Source),
|
|
DestLinkAddress)) {
|
|
//
|
|
// No entry found in ND cache.
|
|
// Queuing for ND does not seem correct -
|
|
// just drop the solicit.
|
|
//
|
|
ICMPv6OutStats.icmps_errors++;
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
|
|
"NeighborSolicitReceive: Didn't find NCE!?!\n"));
|
|
IPv6FreePacket(AdvertPacket);
|
|
goto Return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsNTE(TargetNTEorIF)) {
|
|
//
|
|
// Take our source address from the target NTE.
|
|
//
|
|
AdvertIP->Source = CastToNTE(TargetNTEorIF)->Address;
|
|
}
|
|
else {
|
|
NetTableEntry *BestNTE;
|
|
|
|
//
|
|
// Find the best source address for the destination.
|
|
//
|
|
BestNTE = FindBestSourceAddress(IF, AlignAddr(&AdvertIP->Dest));
|
|
if (BestNTE == NULL) {
|
|
ICMPv6OutStats.icmps_errors++;
|
|
goto Return;
|
|
}
|
|
AdvertIP->Source = BestNTE->Address;
|
|
ReleaseNTE(BestNTE);
|
|
}
|
|
|
|
//
|
|
// Take the target address from the solicitation.
|
|
//
|
|
AdvertTargetAddress = (IPv6Addr *)
|
|
((char *)AdvertICMP + sizeof(ICMPv6Header) + sizeof(ulong));
|
|
*AdvertTargetAddress = *TargetAddress;
|
|
|
|
if (PayloadLength != 24) {
|
|
//
|
|
// Include target link-layer address option.
|
|
//
|
|
AdvertTargetOption = (void *)((char *)AdvertTargetAddress +
|
|
sizeof(IPv6Addr));
|
|
|
|
((uchar *)AdvertTargetOption)[0] = ND_OPTION_TARGET_LINK_LAYER_ADDRESS;
|
|
((uchar *)AdvertTargetOption)[1] = (uchar)((IF->LinkAddressLength + 2 + 7) >> 3);
|
|
|
|
(*IF->WriteLLOpt)(IF->LinkContext, AdvertTargetOption, IF->LinkAddress);
|
|
//
|
|
// We set the override flag when including the TLLA option,
|
|
// unless the target is an anycast address.
|
|
//
|
|
if (TargetType != ADE_ANYCAST)
|
|
Flags |= ND_NA_FLAG_OVERRIDE;
|
|
}
|
|
|
|
//
|
|
// Fill in Flags field in advertisement.
|
|
//
|
|
*(ulong UNALIGNED *)((char *)AdvertICMP + sizeof(ICMPv6Header)) =
|
|
net_long(Flags);
|
|
|
|
//
|
|
// Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message
|
|
// starting with the ICMPv6 header, plus the IPv6 pseudo-header.
|
|
//
|
|
AdvertICMP->Checksum = ChecksumPacket(
|
|
AdvertPacket, Offset + sizeof *AdvertIP, NULL, PayloadLength,
|
|
AlignAddr(&AdvertIP->Source), AlignAddr(&AdvertIP->Dest),
|
|
IP_PROTOCOL_ICMPv6);
|
|
ASSERT(AdvertICMP->Checksum != 0);
|
|
|
|
ICMPv6OutStats.icmps_typecount[ICMPv6_NEIGHBOR_ADVERT]++;
|
|
if (TargetType == ADE_ANYCAST) {
|
|
LARGE_INTEGER Delay;
|
|
//
|
|
// Delay randomly before sending the Neighbor Advertisement.
|
|
//
|
|
Delay.QuadPart = - (LONGLONG)
|
|
RandomNumber(0, MAX_ANYCAST_DELAY_TIME * 10000000);
|
|
Status = IPv6SendLater(Delay,
|
|
IF, AdvertPacket, Offset, DestLinkAddress);
|
|
if (Status != NDIS_STATUS_SUCCESS)
|
|
IPv6SendComplete(NULL, AdvertPacket, IP_NO_RESOURCES);
|
|
}
|
|
else {
|
|
//
|
|
// Transmit the Neighbor Advertisement immediately.
|
|
//
|
|
IPv6SendLL(IF, AdvertPacket, Offset, DestLinkAddress);
|
|
}
|
|
|
|
Return:
|
|
if (IsNTE(TargetNTEorIF))
|
|
ReleaseNTE(CastToNTE(TargetNTEorIF));
|
|
else
|
|
ReleaseIF(CastToIF(TargetNTEorIF));
|
|
}
|
|
|
|
|
|
//* NeighborAdvertReceive - Handle Neighbor Advertisement messages.
|
|
//
|
|
// Validate message and update neighbor cache if necessary.
|
|
//
|
|
void
|
|
NeighborAdvertReceive(
|
|
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
|
|
ICMPv6Header UNALIGNED *ICMP) // ICMP header.
|
|
{
|
|
Interface *IF = Packet->NTEorIF->IF;
|
|
ulong Flags;
|
|
const IPv6Addr *TargetAddress;
|
|
const void *TargetLinkAddress;
|
|
NetTableEntryOrInterface *TargetNTEorIF;
|
|
ushort TargetType;
|
|
|
|
//
|
|
// Validate the advertisement.
|
|
// By the time we get here, any IPv6 Authentication Header will have
|
|
// already been checked, as will have the ICMPv6 checksum. Still need
|
|
// to check the IP Hop Limit, and the ICMP code and length.
|
|
//
|
|
if ((Packet->IP->HopLimit != 255) ||
|
|
(Packet->Flags & PACKET_TUNNELED)) {
|
|
//
|
|
// Packet was forwarded by a router, therefore it cannot be
|
|
// from a legitimate neighbor. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received a routed neighbor advertisement\n"));
|
|
return;
|
|
}
|
|
if (ICMP->Code != 0) {
|
|
//
|
|
// Bogus/corrupted neighbor advertisement message. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received a corrupted neighbor advertisement\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Next we have a 4 byte field, then an IPv6 address.
|
|
//
|
|
if (! PacketPullup(Packet, 4 + sizeof(IPv6Addr),
|
|
__builtin_alignof(IPv6Addr), 0)) {
|
|
if (Packet->TotalSize < 4 + sizeof(IPv6Addr)) {
|
|
//
|
|
// Packet too short to contain minimal advertisement.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received a too short advertisement\n"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Next 4 bytes contains 3 bits of flags and 29 bits of "Reserved".
|
|
// Pull this out as a 32 bit long, we're required to ignore the "Reserved"
|
|
// bits. Get a pointer to the target address which follows, then skip
|
|
// over that.
|
|
//
|
|
Flags = net_long(*(ulong UNALIGNED *)Packet->Data);
|
|
TargetAddress = AlignAddr((IPv6Addr UNALIGNED *)
|
|
((ulong *)Packet->Data + 1));
|
|
AdjustPacketParams(Packet, sizeof(ulong) + sizeof(IPv6Addr));
|
|
|
|
//
|
|
// Check that the target address is not a multicast address.
|
|
//
|
|
if (IsMulticast(TargetAddress)) {
|
|
//
|
|
// Nodes are not supposed to send neighbor advertisements for
|
|
// multicast addresses. We're required to drop any such advertisements
|
|
// we receive.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received advertisement for a multicast address\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check that Solicited flag is zero
|
|
// if the destination address is multicast.
|
|
//
|
|
if ((Flags & ND_NA_FLAG_SOLICITED) &&
|
|
IsMulticast(AlignAddr(&Packet->IP->Dest))) {
|
|
//
|
|
// We're required to drop the advertisement.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received solicited advertisement to a multicast address\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The code below assumes a contiguous buffer for all the options
|
|
// (the remainder of the packet). If that isn't currently the
|
|
// case, do a pullup for the whole thing.
|
|
//
|
|
if (! PacketPullup(Packet, Packet->TotalSize, 1, 0)) {
|
|
// Can only fail if we run out of memory.
|
|
return;
|
|
}
|
|
|
|
ASSERT(Packet->ContigSize == Packet->TotalSize);
|
|
|
|
//
|
|
// Look for a target link-layer address option.
|
|
//
|
|
TargetLinkAddress = NULL;
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength;
|
|
|
|
//
|
|
// Validate the option length.
|
|
//
|
|
if ((Packet->ContigSize < 8) ||
|
|
((OptionLength = *((uchar *)Packet->Data + 1) << 3) == 0) ||
|
|
(OptionLength > Packet->ContigSize)) {
|
|
//
|
|
// Invalid option length. We MUST silently drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received option with bogus length\n"));
|
|
return;
|
|
}
|
|
|
|
if (*((uchar *)Packet->Data) == ND_OPTION_TARGET_LINK_LAYER_ADDRESS) {
|
|
//
|
|
// Some interfaces do not use SLLA and TLLA options.
|
|
// For example, see RFC 2893 section 3.8.
|
|
//
|
|
if (IF->ReadLLOpt != NULL) {
|
|
//
|
|
// Parse the link-layer address option.
|
|
//
|
|
TargetLinkAddress = (*IF->ReadLLOpt)(IF->LinkContext,
|
|
(uchar *)Packet->Data);
|
|
if (TargetLinkAddress == NULL) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"NeighborAdvertReceive: "
|
|
"Received bogus ll option\n"));
|
|
return;
|
|
}
|
|
//
|
|
// Note that if there are multiple options for some bogus
|
|
// reason, we use the last one. We sanity-check all the
|
|
// options.
|
|
//
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
//
|
|
AdjustPacketParams(Packet, OptionLength);
|
|
}
|
|
|
|
//
|
|
// We've received and parsed a valid neighbor advertisement.
|
|
//
|
|
// First, we check our Duplicate Address Detection state.
|
|
// (See RFC 2461 section 5.4.4.)
|
|
//
|
|
TargetNTEorIF = FindAddressOnInterface(IF, TargetAddress, &TargetType);
|
|
if (TargetNTEorIF == NULL) {
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"NeighborAdvertReceive: disabled IF\n"));
|
|
return;
|
|
}
|
|
|
|
if (IsNTE(TargetNTEorIF)) {
|
|
NetTableEntry *TargetNTE = CastToNTE(TargetNTEorIF);
|
|
|
|
if (TargetType == ADE_UNICAST) {
|
|
ASSERT(IP6_ADDR_EQUAL(TargetAddress, &TargetNTE->Address));
|
|
|
|
//
|
|
// Someone out there appears to be using our address;
|
|
// they responded to our DAD solicit.
|
|
//
|
|
// The RFC says to declare a duplicate only if our address is
|
|
// tentative, whereas we also make our address duplicate whenever
|
|
// the override bit in the advertisement is set. This is because
|
|
// we redo DAD for existing addresses upon reconnection.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
|
|
"NeighborAdvertReceive: DAD found duplicate\n"));
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
if (IsTentativeNTE(TargetNTE) || (Flags & ND_NA_FLAG_OVERRIDE))
|
|
AddrConfDuplicate(IF, TargetNTE);
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
|
|
//
|
|
// We continue with normal processing.
|
|
// For example, we may have an invalid NTE for the address
|
|
// and we are trying to communicate with the address,
|
|
// which is currently assigned to another node.
|
|
//
|
|
}
|
|
|
|
ReleaseNTE(TargetNTE);
|
|
}
|
|
else {
|
|
Interface *TargetIF = CastToIF(TargetNTEorIF);
|
|
|
|
ASSERT(TargetType != ADE_UNICAST);
|
|
ASSERT(TargetIF == IF);
|
|
ReleaseIF(TargetIF);
|
|
}
|
|
|
|
//
|
|
// If this is a point-to-point interface,
|
|
// then we know the target link-layer address.
|
|
//
|
|
if ((TargetLinkAddress == NULL) &&
|
|
(IF->Flags & IF_FLAG_P2P)) {
|
|
//
|
|
// Use the link-layer address of the other endpoint,
|
|
// which follows our link-layer address in memory.
|
|
//
|
|
TargetLinkAddress = IF->LinkAddress + IF->LinkAddressLength;
|
|
}
|
|
|
|
//
|
|
// Process the advertisement.
|
|
//
|
|
NeighborCacheAdvert(IF, TargetAddress, TargetLinkAddress, Flags);
|
|
}
|
|
|
|
|
|
//* RedirectReceive - Handle Redirect messages.
|
|
//
|
|
// See RFC 1970, sections 8.1 and 8.3.
|
|
//
|
|
void
|
|
RedirectReceive(
|
|
IPv6Packet *Packet, // Packet handed to us by ICMPv6Receive.
|
|
ICMPv6Header UNALIGNED *ICMP) // ICMP header.
|
|
{
|
|
Interface *IF = Packet->NTEorIF->IF;
|
|
const IPv6Addr *TargetAddress;
|
|
const void *TargetLinkAddress;
|
|
const IPv6Addr *DestinationAddress;
|
|
NeighborCacheEntry *TargetNCE;
|
|
IP_STATUS Status;
|
|
|
|
//
|
|
// Ignore the redirect if this is a forwarding interface.
|
|
//
|
|
if (IF->Flags & IF_FLAG_FORWARDS)
|
|
return;
|
|
|
|
//
|
|
// Validate the redirect.
|
|
// By the time we get here, any IPv6 Authentication Header will have
|
|
// already been checked, as will have the ICMPv6 checksum. Still need
|
|
// to check the IP Hop Limit, and the ICMP code and length.
|
|
//
|
|
if ((Packet->IP->HopLimit != 255) ||
|
|
(Packet->Flags & PACKET_TUNNELED)) {
|
|
//
|
|
// Packet was forwarded by a router, therefore it cannot be
|
|
// from a legitimate neighbor. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: Received a routed redirect\n"));
|
|
return;
|
|
}
|
|
if (ICMP->Code != 0) {
|
|
//
|
|
// Bogus/corrupted redirect message. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: Received a corrupted redirect\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check that the source address is a link-local address.
|
|
// We need a link-local source address to identify the router
|
|
// that sent the redirect.
|
|
//
|
|
if (!IsLinkLocal(AlignAddr(&Packet->IP->Source))) {
|
|
//
|
|
// Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: "
|
|
"Received redirect from non-link-local source\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Next we have a 4 byte reserved field, then two IPv6 addresses.
|
|
//
|
|
if (! PacketPullup(Packet, 4 + 2 * sizeof(IPv6Addr),
|
|
__builtin_alignof(IPv6Addr), 0)) {
|
|
if (Packet->TotalSize < 4 + 2 * sizeof(IPv6Addr)) {
|
|
//
|
|
// Packet too short to contain minimal redirect.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: Received a too short redirect\n"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Skip over the 4 byte reserved field.
|
|
// Pick up the Target and Destination addresses.
|
|
//
|
|
AdjustPacketParams(Packet, 4);
|
|
TargetAddress = AlignAddr((IPv6Addr UNALIGNED *)Packet->Data);
|
|
DestinationAddress = TargetAddress + 1;
|
|
AdjustPacketParams(Packet, 2 * sizeof(IPv6Addr));
|
|
|
|
//
|
|
// Check that the destination address is not a multicast address.
|
|
//
|
|
if (IsMulticast(DestinationAddress)) {
|
|
//
|
|
// Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: "
|
|
"Received redirect for multicast address\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check that either the target address is link-local
|
|
// (redirecting to a router) or the target and the destination
|
|
// are the same (redirecting to an on-link destination).
|
|
//
|
|
if (!IsLinkLocal(TargetAddress) &&
|
|
!IP6_ADDR_EQUAL(TargetAddress, DestinationAddress)) {
|
|
//
|
|
// Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: "
|
|
"Received redirect with non-link-local target\n"));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The code below assumes a contiguous buffer for all the options
|
|
// (the remainder of the packet). If that isn't currently the
|
|
// case, do a pullup for the whole thing.
|
|
//
|
|
if (! PacketPullup(Packet, Packet->TotalSize, 1, 0)) {
|
|
// Can only fail if we run out of memory.
|
|
return;
|
|
}
|
|
|
|
ASSERT(Packet->ContigSize == Packet->TotalSize);
|
|
|
|
//
|
|
// Look for a target link-layer address option,
|
|
// checking that all included options have a valid length.
|
|
//
|
|
TargetLinkAddress = NULL;
|
|
while (Packet->ContigSize) {
|
|
uint OptionLength = 0;
|
|
|
|
//
|
|
// Validate the option length.
|
|
//
|
|
if ((Packet->ContigSize < 8) ||
|
|
((OptionLength = *((uchar *)Packet->Data + 1) << 3) == 0) ||
|
|
(OptionLength > Packet->ContigSize)) {
|
|
//
|
|
// Invalid option length. We MUST silently drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: Received option with length %u "
|
|
"while we only have %u data\n", OptionLength,
|
|
Packet->ContigSize));
|
|
return;
|
|
}
|
|
|
|
if (*(uchar *)Packet->Data == ND_OPTION_TARGET_LINK_LAYER_ADDRESS) {
|
|
//
|
|
// Some interfaces do not use SLLA and TLLA options.
|
|
// For example, see RFC 2893 section 3.8.
|
|
//
|
|
if (IF->ReadLLOpt != NULL) {
|
|
//
|
|
// Parse the link-layer address option.
|
|
//
|
|
TargetLinkAddress = (*IF->ReadLLOpt)(IF->LinkContext,
|
|
(uchar *)Packet->Data);
|
|
if (TargetLinkAddress == NULL) {
|
|
//
|
|
// Invalid option format. Drop the packet.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
|
|
"RedirectReceive: Received bogus ll option\n"));
|
|
return;
|
|
}
|
|
//
|
|
// Note that if there are multiple options for some bogus
|
|
// reason, we use the last one. We must sanity-check all
|
|
// option lengths.
|
|
//
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move forward to next option.
|
|
//
|
|
AdjustPacketParams(Packet, OptionLength);
|
|
}
|
|
|
|
//
|
|
// We have a valid redirect message (except for checking that the
|
|
// source of the redirect is the current first-hop neighbor
|
|
// for the destination - RedirectRouteCache does that).
|
|
//
|
|
// First we get an NCE for the target of the redirect. Then we
|
|
// update the route cache. If RedirectRouteCache doesn't invalidate
|
|
// the redirect, then we update the neighbor cache.
|
|
//
|
|
|
|
//
|
|
// Get the Neighbor Cache Entry for the target.
|
|
//
|
|
TargetNCE = FindOrCreateNeighbor(IF, TargetAddress);
|
|
if (TargetNCE == NULL) {
|
|
//
|
|
// Couldn't find or create NCE. Drop the packet.
|
|
//
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Update the route cache to reflect this redirect.
|
|
//
|
|
Status = RedirectRouteCache(AlignAddr(&Packet->IP->Source),
|
|
DestinationAddress,
|
|
IF, TargetNCE);
|
|
|
|
if (Status == IP_SUCCESS) {
|
|
//
|
|
// Update the Neighbor Cache Entry for the target.
|
|
// We know the target is a router if the target address
|
|
// is not the same as the destination address.
|
|
//
|
|
NeighborCacheUpdate(TargetNCE, TargetLinkAddress,
|
|
!IP6_ADDR_EQUAL(TargetAddress,
|
|
DestinationAddress));
|
|
}
|
|
|
|
ReleaseNCE(TargetNCE);
|
|
}
|
|
|
|
|
|
//* RouterSolicitSend
|
|
//
|
|
// Sends a Router Solicitation.
|
|
// The solicit is always sent to the all-routers multicast address.
|
|
// Chooses a valid source address for the interface.
|
|
//
|
|
// Called with no locks held.
|
|
//
|
|
void
|
|
RouterSolicitSend(Interface *IF)
|
|
{
|
|
NDIS_STATUS Status;
|
|
NDIS_PACKET *Packet;
|
|
int PayloadLength;
|
|
uint Offset;
|
|
void *Mem;
|
|
uint MemLen;
|
|
IPv6Header UNALIGNED *IP;
|
|
ICMPv6Header UNALIGNED *ICMP;
|
|
void *SourceOption;
|
|
void *LLDest;
|
|
IPv6Addr Source;
|
|
|
|
//
|
|
// Calculate the link-level destination address.
|
|
// (The IPv6 destination is a multicast address.)
|
|
// This can potentially fail, such as with ISATAP when
|
|
// no router is known.
|
|
//
|
|
LLDest = alloca(IF->LinkAddressLength);
|
|
if ((*IF->ConvertAddr)(IF->LinkContext, &AllRoutersOnLinkAddr, LLDest) ==
|
|
ND_STATE_INCOMPLETE) {
|
|
|
|
//
|
|
// Don't count this as an attempt or a failure. Just pretend
|
|
// that router discovery is disabled. This way, if you aren't
|
|
// using ISATAP, you don't see a high ICMP error count.
|
|
//
|
|
return;
|
|
}
|
|
|
|
ICMPv6OutStats.icmps_msgs++;
|
|
|
|
//
|
|
// Allocate a packet for solicitation.
|
|
// In addition to the 8 bytes for the solicit proper, leave space
|
|
// for source link-layer address option (round option length up to
|
|
// 8-byte multiple) IF we have a valid source address and ND is
|
|
// enabled. If ND is disabled, the router can presumably
|
|
// derive our link-layer address itself.
|
|
//
|
|
PayloadLength = 8;
|
|
if (GetLinkLocalAddress(IF, &Source) && (IF->WriteLLOpt != NULL)) {
|
|
|
|
PayloadLength += (IF->LinkAddressLength + 2 + 7) &~ 7;
|
|
}
|
|
Offset = IF->LinkHeaderSize;
|
|
MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
|
|
|
|
Status = IPv6AllocatePacket(MemLen, &Packet, &Mem);
|
|
if (Status != NDIS_STATUS_SUCCESS) {
|
|
ICMPv6OutStats.icmps_errors++;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Prepare IP header of solicitation.
|
|
//
|
|
IP = (IPv6Header UNALIGNED *)((uchar *)Mem + Offset);
|
|
IP->VersClassFlow = IP_VERSION;
|
|
IP->PayloadLength = net_short((ushort)PayloadLength);
|
|
IP->NextHeader = IP_PROTOCOL_ICMPv6;
|
|
IP->HopLimit = 255;
|
|
IP->Source = Source;
|
|
IP->Dest = AllRoutersOnLinkAddr;
|
|
|
|
//
|
|
// Prepare ICMP header.
|
|
//
|
|
ICMP = (ICMPv6Header UNALIGNED *)(IP + 1);
|
|
ICMP->Type = ICMPv6_ROUTER_SOLICIT;
|
|
ICMP->Code = 0;
|
|
ICMP->Checksum = 0;
|
|
|
|
//
|
|
// Must zero the reserved field.
|
|
//
|
|
*(ulong UNALIGNED *)(ICMP + 1) = 0;
|
|
|
|
if (PayloadLength != 8) {
|
|
//
|
|
// Include source link-layer address option.
|
|
//
|
|
SourceOption = (void *)((ulong *)(ICMP + 1) + 1);
|
|
|
|
((uchar *)SourceOption)[0] = ND_OPTION_SOURCE_LINK_LAYER_ADDRESS;
|
|
((uchar *)SourceOption)[1] = (uchar)((IF->LinkAddressLength + 2 + 7) >> 3);
|
|
|
|
(*IF->WriteLLOpt)(IF->LinkContext, SourceOption, IF->LinkAddress);
|
|
}
|
|
|
|
//
|
|
// Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message
|
|
// starting with the ICMPv6 header, plus the IPv6 pseudo-header.
|
|
//
|
|
ICMP->Checksum = ChecksumPacket(
|
|
Packet, Offset + sizeof *IP, NULL, PayloadLength,
|
|
AlignAddr(&IP->Source), AlignAddr(&IP->Dest),
|
|
IP_PROTOCOL_ICMPv6);
|
|
ASSERT(ICMP->Checksum != 0);
|
|
|
|
//
|
|
// We prevent loopback of all ND packets.
|
|
//
|
|
PC(Packet)->Flags = NDIS_FLAGS_MULTICAST_PACKET | NDIS_FLAGS_DONT_LOOPBACK;
|
|
|
|
//
|
|
// Transmit the packet.
|
|
//
|
|
ICMPv6OutStats.icmps_typecount[ICMPv6_ROUTER_SOLICIT]++;
|
|
IPv6SendLL(IF, Packet, Offset, LLDest);
|
|
}
|
|
|
|
//* RouterSolicitTimeout
|
|
//
|
|
// Called periodically from IPv6Timeout to handle timeouts
|
|
// for sending router solicitations for an interface.
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
void
|
|
RouterSolicitTimeout(Interface *IF)
|
|
{
|
|
int SendRouterSolicit = FALSE;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
|
|
if (IF->RSTimer != 0) {
|
|
ASSERT(!IsDisabledIF(IF));
|
|
|
|
//
|
|
// Timer is running. Decrement and check for expiration.
|
|
//
|
|
if (--IF->RSTimer == 0) {
|
|
if (IF->RSCount < MAX_RTR_SOLICITATIONS) {
|
|
//
|
|
// Re-arm the timer and generate a router solicitation.
|
|
//
|
|
IF->RSTimer = RTR_SOLICITATION_INTERVAL;
|
|
IF->RSCount++;
|
|
SendRouterSolicit = TRUE;
|
|
}
|
|
else {
|
|
//
|
|
// If we are still in the reconnecting state,
|
|
// meaning we have not received an RA
|
|
// since reconnecting to the link,
|
|
// remove stale auto-configured state.
|
|
//
|
|
if (IF->Flags & IF_FLAG_MEDIA_RECONNECTED) {
|
|
IF->Flags &= ~IF_FLAG_MEDIA_RECONNECTED;
|
|
|
|
//
|
|
// Remove auto-configured addresses.
|
|
//
|
|
AddrConfResetAutoConfig(IF, 0);
|
|
|
|
//
|
|
// Similarly, remove auto-configured routes.
|
|
//
|
|
RouteTableResetAutoConfig(IF, 0);
|
|
|
|
//
|
|
// Restore interface parameters.
|
|
//
|
|
InterfaceResetAutoConfig(IF);
|
|
}
|
|
|
|
//
|
|
// On a non-multicast capable interface, we'll
|
|
// never get multicast RA's so change to infrequent
|
|
// RS polling.
|
|
//
|
|
if (!(IF->Flags & IF_FLAG_MULTICAST)) {
|
|
IF->RSTimer = SLOW_RTR_SOLICITATION_INTERVAL;
|
|
IF->RSCount++;
|
|
SendRouterSolicit = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
|
|
if (SendRouterSolicit)
|
|
RouterSolicitSend(IF);
|
|
}
|
|
|
|
//* NeighborSolicitSend0
|
|
//
|
|
// Low-level version of NeighborSolicitSend -
|
|
// uses explicit source/destination/target addresses
|
|
// instead of an NCE.
|
|
//
|
|
void
|
|
NeighborSolicitSend0(
|
|
Interface *IF, // Interface for the solicit
|
|
void *LLDest, // Link-level destination
|
|
const IPv6Addr *Source, // IP-level source
|
|
const IPv6Addr *Dest, // IP-level destination
|
|
const IPv6Addr *Target) // IP-level target of the solicit
|
|
{
|
|
NDIS_STATUS Status;
|
|
NDIS_PACKET *Packet;
|
|
int PayloadLength;
|
|
uint Offset;
|
|
void *Mem;
|
|
uint MemLen;
|
|
IPv6Header UNALIGNED *IP;
|
|
ICMPv6Header UNALIGNED *ICMP;
|
|
IPv6Addr *TargetAddress;
|
|
void *SourceOption;
|
|
|
|
ICMPv6OutStats.icmps_msgs++;
|
|
|
|
//
|
|
// Allocate a packet for solicitation.
|
|
// In addition to the 24 bytes for the solicit proper, leave space
|
|
// for source link-layer address option (round option length up to
|
|
// 8-byte multiple) IF we have a valid source address.
|
|
//
|
|
PayloadLength = 24;
|
|
if (!IsUnspecified(Source) && (IF->WriteLLOpt != NULL))
|
|
PayloadLength += (IF->LinkAddressLength + 2 + 7) &~ 7;
|
|
Offset = IF->LinkHeaderSize;
|
|
MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
|
|
|
|
Status = IPv6AllocatePacket(MemLen, &Packet, &Mem);
|
|
if (Status != NDIS_STATUS_SUCCESS) {
|
|
ICMPv6OutStats.icmps_errors++;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Prepare IP header of solicitation.
|
|
//
|
|
IP = (IPv6Header UNALIGNED *)((uchar *)Mem + Offset);
|
|
IP->VersClassFlow = IP_VERSION;
|
|
IP->PayloadLength = net_short((ushort)PayloadLength);
|
|
IP->NextHeader = IP_PROTOCOL_ICMPv6;
|
|
IP->HopLimit = 255;
|
|
IP->Source = *Source;
|
|
IP->Dest = *Dest;
|
|
|
|
//
|
|
// Prepare ICMP header.
|
|
//
|
|
ICMP = (ICMPv6Header UNALIGNED *)(IP + 1);
|
|
ICMP->Type = ICMPv6_NEIGHBOR_SOLICIT;
|
|
ICMP->Code = 0;
|
|
ICMP->Checksum = 0;
|
|
|
|
//
|
|
// Must zero the reserved field.
|
|
//
|
|
*(ulong UNALIGNED *)(ICMP + 1) = 0;
|
|
|
|
//
|
|
// Initialize the target address.
|
|
//
|
|
TargetAddress = (IPv6Addr *)((ulong *)(ICMP + 1) + 1);
|
|
*TargetAddress = *Target;
|
|
|
|
if (PayloadLength != 24) {
|
|
//
|
|
// Include source link-layer address option.
|
|
//
|
|
SourceOption = (void *)(TargetAddress + 1);
|
|
|
|
((uchar *)SourceOption)[0] = ND_OPTION_SOURCE_LINK_LAYER_ADDRESS;
|
|
((uchar *)SourceOption)[1] = (uchar)((IF->LinkAddressLength + 2 + 7) >> 3);
|
|
|
|
(*IF->WriteLLOpt)(IF->LinkContext, SourceOption, IF->LinkAddress);
|
|
}
|
|
|
|
//
|
|
// Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message
|
|
// starting with the ICMPv6 header, plus the IPv6 pseudo-header.
|
|
//
|
|
ICMP->Checksum = ChecksumPacket(
|
|
Packet, Offset + sizeof *IP, NULL, PayloadLength,
|
|
AlignAddr(&IP->Source), AlignAddr(&IP->Dest),
|
|
IP_PROTOCOL_ICMPv6);
|
|
ASSERT(ICMP->Checksum != 0);
|
|
|
|
//
|
|
// Is this a multicast packet?
|
|
// But we inhibit loopback of unicast packets too -
|
|
// this prevents rare races in which we receive
|
|
// our own ND packets.
|
|
//
|
|
PC(Packet)->Flags = NDIS_FLAGS_DONT_LOOPBACK;
|
|
if (IsMulticast(Dest))
|
|
PC(Packet)->Flags |= NDIS_FLAGS_MULTICAST_PACKET;
|
|
|
|
//
|
|
// Transmit the packet.
|
|
//
|
|
ICMPv6OutStats.icmps_typecount[ICMPv6_NEIGHBOR_SOLICIT]++;
|
|
IPv6SendLL(IF, Packet, Offset, LLDest);
|
|
}
|
|
|
|
//* NeighborSolicitSend - Send a Neighbor Solicitation message.
|
|
//
|
|
// Chooses source/destination/target addresses depending
|
|
// on the NCE state and sends the solicit packet.
|
|
//
|
|
// Called with NO locks held.
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
NeighborSolicitSend(
|
|
NeighborCacheEntry *NCE, // Neighbor to solicit.
|
|
const IPv6Addr *Source) // Source address to send from (optional).
|
|
{
|
|
Interface *IF = NCE->IF;
|
|
IPv6Addr Dest;
|
|
void *LLDest;
|
|
IPv6Addr LinkLocal;
|
|
|
|
//
|
|
// Check Neighbor Discovery protocol state of our neighbor in order to
|
|
// determine whether we should multicast or unicast our solicitation.
|
|
//
|
|
// Note that we do not take the neighbor cache lock to make this check.
|
|
// The worst that can happen is that we'll think the NDState is not
|
|
// incomplete and then use a bogus/changing LinkAddress.
|
|
// This is rare enough and benign enough to be OK.
|
|
// Similar reasoning in IPv6SendND.
|
|
//
|
|
if (NCE->NDState == ND_STATE_INCOMPLETE) {
|
|
//
|
|
// This is our initial solicitation to this neighbor, so we don't
|
|
// have a link-layer address cached. Multicast our solicitation
|
|
// to the solicited-node multicast address corresponding to our
|
|
// neighbor's address.
|
|
//
|
|
CreateSolicitedNodeMulticastAddress(&NCE->NeighborAddress, &Dest);
|
|
|
|
LLDest = alloca(IF->LinkAddressLength);
|
|
(*IF->ConvertAddr)(IF->LinkContext, &Dest, LLDest);
|
|
} else {
|
|
//
|
|
// We have a cached link-layer address that has gone stale.
|
|
// Probe this address via a unicast solicitation.
|
|
//
|
|
Dest = NCE->NeighborAddress;
|
|
|
|
LLDest = NCE->LinkAddress;
|
|
}
|
|
|
|
//
|
|
// If we were given a specific source address to use then do so (normal
|
|
// case for our initial multicasted solicit), otherwise use the outgoing
|
|
// interface's link-local address. If there is no valid link-local
|
|
// address, then we give up.
|
|
//
|
|
if (Source == NULL) {
|
|
if (!GetLinkLocalAddress(IF, &LinkLocal)) {
|
|
return;
|
|
}
|
|
Source = &LinkLocal;
|
|
}
|
|
|
|
//
|
|
// Now that we've determined the source/destination/target addresses,
|
|
// we can actually send the solicit.
|
|
//
|
|
NeighborSolicitSend0(IF, LLDest, Source, &Dest, &NCE->NeighborAddress);
|
|
}
|
|
|
|
|
|
//* DADSolicitSend - Sends a DAD Neighbor Solicit.
|
|
//
|
|
// Like NeighborSolicitSend, but specialized for DAD.
|
|
//
|
|
void
|
|
DADSolicitSend(NetTableEntry *NTE)
|
|
{
|
|
Interface *IF = NTE->IF;
|
|
IPv6Addr Dest;
|
|
void *LLDest;
|
|
|
|
//
|
|
// Multicast our solicitation to the solicited-node multicast
|
|
// address corresponding to our own address.
|
|
//
|
|
CreateSolicitedNodeMulticastAddress(&NTE->Address, &Dest);
|
|
|
|
//
|
|
// Convert the IP-level multicast destination address
|
|
// to a link-level multicast address.
|
|
//
|
|
LLDest = alloca(IF->LinkAddressLength);
|
|
(*IF->ConvertAddr)(IF->LinkContext, &Dest, LLDest);
|
|
|
|
//
|
|
// Now that we've created the destination addresses,
|
|
// we can actually send the solicit.
|
|
//
|
|
NeighborSolicitSend0(IF, LLDest, &UnspecifiedAddr,
|
|
&Dest, &NTE->Address);
|
|
}
|
|
|
|
|
|
//* DADTimeout - handle a Duplicate Address Detection timeout.
|
|
//
|
|
// Called from IPv6Timeout to handle DAD timeouts on an NTE.
|
|
//
|
|
void
|
|
DADTimeout(NetTableEntry *NTE)
|
|
{
|
|
Interface *IF = NTE->IF;
|
|
int SendDADSolicit = FALSE;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
|
|
if (NTE->DADTimer != 0) {
|
|
ASSERT(NTE->DADState != DAD_STATE_INVALID);
|
|
|
|
//
|
|
// Timer is running. Decrement and check for expiration.
|
|
//
|
|
if (--NTE->DADTimer == 0) {
|
|
//
|
|
// Timer went off.
|
|
//
|
|
|
|
if (NTE->DADCount == 0) {
|
|
//
|
|
// The address has passed Duplicate Address Detection.
|
|
// Because we have passed DAD, reset the failure count.
|
|
//
|
|
IF->DupAddrDetects = 0;
|
|
AddrConfNotDuplicate(IF, NTE);
|
|
}
|
|
else {
|
|
//
|
|
// Time to send another solicit.
|
|
//
|
|
NTE->DADCount--;
|
|
NTE->DADTimer = (ushort)IF->RetransTimer;
|
|
SendDADSolicit = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
|
|
if (SendDADSolicit)
|
|
DADSolicitSend(NTE);
|
|
}
|
|
|
|
//* CalcReachableTime
|
|
//
|
|
// Calculate a pseudo-random ReachableTime from BaseReachableTime
|
|
// (this prevents synchronization of Neighbor Unreachability Detection
|
|
// messages from different hosts), and convert it to units of IPv6
|
|
// timer ticks (cheaper to do once here than at every packet send).
|
|
//
|
|
uint // IPv6 timer ticks
|
|
CalcReachableTime(
|
|
uint BaseReachableTime) // Learned from Router Advertisements (in ms).
|
|
{
|
|
uint Factor;
|
|
uint ReachableTime;
|
|
|
|
//
|
|
// Calculate a uniformly-distributed random value between
|
|
// MIN_RANDOM_FACTOR and MAX_RANDOM_FACTOR of the BaseReachableTime.
|
|
// To keep the arithmetic integer, *_RANDOM_FACTOR (and thus the
|
|
// 'Factor' variable) are defined as percentage values.
|
|
//
|
|
Factor = RandomNumber(MIN_RANDOM_FACTOR, MAX_RANDOM_FACTOR);
|
|
|
|
//
|
|
// Now that we have a random value picked out of our percentage spread,
|
|
// take that percentage of the BaseReachableTime.
|
|
//
|
|
// BaseReachableTime has a maximum value of 3,600,000 milliseconds
|
|
// (see RFC 1970, section 6.2.1), so Factor would have to exeed 1100 %
|
|
// in order to overflow a 32-bit unsigned integer.
|
|
//
|
|
ReachableTime = (BaseReachableTime * Factor) / 100;
|
|
|
|
//
|
|
// Convert from milliseconds (which is what BaseReachableTime is in) to
|
|
// IPv6 timer ticks (which is what we keep ReachableTime in).
|
|
//
|
|
return ConvertMillisToTicks(ReachableTime);
|
|
}
|
|
|
|
//* RedirectSend
|
|
//
|
|
// Send a Redirect message to a neighbor,
|
|
// telling it to use a better first-hop neighbor
|
|
// in the future for the specified destination.
|
|
//
|
|
// RecvNTEorIF (optionally) specifies a source address
|
|
// to use in the Redirect message.
|
|
//
|
|
// Packet (optionally) specifies data to include
|
|
// in a Redirected Header option.
|
|
//
|
|
// Called with NO locks held.
|
|
// Callable from thread or DPC context.
|
|
//
|
|
void
|
|
RedirectSend(
|
|
NeighborCacheEntry *NCE, // Neighbor getting the Redirect.
|
|
NeighborCacheEntry *TargetNCE, // Better first-hop to use
|
|
const IPv6Addr *Destination, // for this Destination address.
|
|
NetTableEntryOrInterface *NTEorIF, // Source of the Redirect.
|
|
PNDIS_PACKET FwdPacket, // Packet triggering the redirect.
|
|
uint FwdOffset,
|
|
uint FwdPayloadLength)
|
|
{
|
|
PNDIS_BUFFER FwdBuffer;
|
|
Interface *IF = NCE->IF;
|
|
NDIS_STATUS Status;
|
|
NDIS_PACKET *Packet;
|
|
uint PayloadLength;
|
|
uint TargetOptionLength;
|
|
uint RedirectOptionData;
|
|
uint RedirectOptionLength;
|
|
uint Offset;
|
|
void *Mem;
|
|
uint MemLen;
|
|
IPv6Header UNALIGNED *IP;
|
|
ICMPv6Header UNALIGNED *ICMP;
|
|
void *TargetLinkAddress;
|
|
KIRQL OldIrql;
|
|
const IPv6Addr *Source;
|
|
IPv6Addr LinkLocal;
|
|
int Ok;
|
|
|
|
ASSERT((IF == TargetNCE->IF) && (IF == NTEorIF->IF));
|
|
|
|
//
|
|
// If our caller specified a source address, use it.
|
|
// Otherwise (common case) we use our link-local address.
|
|
//
|
|
if (IsNTE(NTEorIF)) {
|
|
Source = &CastToNTE(NTEorIF)->Address;
|
|
}
|
|
else {
|
|
//
|
|
// We need a valid link-local address to send a redirect.
|
|
//
|
|
if (! GetLinkLocalAddress(IF, &LinkLocal))
|
|
return;
|
|
Source = &LinkLocal;
|
|
}
|
|
|
|
//
|
|
// Do we know the Target neighbor's link address?
|
|
//
|
|
KeAcquireSpinLock(&IF->LockNC, &OldIrql);
|
|
if (TargetNCE->NDState == ND_STATE_INCOMPLETE) {
|
|
TargetLinkAddress = NULL;
|
|
TargetOptionLength = 0;
|
|
}
|
|
else {
|
|
TargetLinkAddress = alloca(IF->LinkAddressLength);
|
|
RtlCopyMemory(TargetLinkAddress, TargetNCE->LinkAddress,
|
|
IF->LinkAddressLength);
|
|
TargetOptionLength = (IF->LinkAddressLength + 2 + 7) &~ 7;
|
|
}
|
|
KeReleaseSpinLock(&IF->LockNC, OldIrql);
|
|
|
|
//
|
|
// Calculate the Redirect's payload size,
|
|
// with space for a Target Link-Layer Address option.
|
|
//
|
|
PayloadLength = 40 + TargetOptionLength;
|
|
|
|
//
|
|
// Allow space for the Redirected Header option,
|
|
// without exceeding the MTU.
|
|
// Note that RFC 2461 4.6.3 explicitly specifies
|
|
// the IPv6 minimum MTU, not the link MTU.
|
|
// We can always include at least the option header and
|
|
// the IPv6 header from FwdPacket.
|
|
//
|
|
RedirectOptionLength = 8 + sizeof(IPv6Header);
|
|
if (sizeof(IPv6Header) + PayloadLength +
|
|
RedirectOptionLength + FwdPayloadLength > IPv6_MINIMUM_MTU) {
|
|
//
|
|
// Truncate FwdPacket to make it fit.
|
|
//
|
|
RedirectOptionLength = IPv6_MINIMUM_MTU -
|
|
(sizeof(IPv6Header) + PayloadLength);
|
|
RedirectOptionData = RedirectOptionLength - 8;
|
|
}
|
|
else {
|
|
//
|
|
// Include all of FwdPacket, plus possible padding.
|
|
//
|
|
RedirectOptionLength += (FwdPayloadLength + 7) &~ 7;
|
|
RedirectOptionData = sizeof(IPv6Header) + FwdPayloadLength;
|
|
}
|
|
PayloadLength += RedirectOptionLength;
|
|
|
|
//
|
|
// Allocate a packet for the Redirect.
|
|
//
|
|
Offset = IF->LinkHeaderSize;
|
|
MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
|
|
Status = IPv6AllocatePacket(MemLen, &Packet, &Mem);
|
|
if (Status != NDIS_STATUS_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Prepare IP header of solicitation.
|
|
//
|
|
IP = (IPv6Header UNALIGNED *)((uchar *)Mem + Offset);
|
|
IP->VersClassFlow = IP_VERSION;
|
|
IP->PayloadLength = net_short((ushort)PayloadLength);
|
|
IP->NextHeader = IP_PROTOCOL_ICMPv6;
|
|
IP->HopLimit = 255;
|
|
IP->Dest = NCE->NeighborAddress;
|
|
IP->Source = *Source;
|
|
|
|
//
|
|
// Prepare ICMP header.
|
|
//
|
|
ICMP = (ICMPv6Header UNALIGNED *)(IP + 1);
|
|
ICMP->Type = ICMPv6_REDIRECT;
|
|
ICMP->Code = 0;
|
|
ICMP->Checksum = 0;
|
|
Mem = (void *)(ICMP + 1);
|
|
|
|
//
|
|
// Must zero the reserved field.
|
|
//
|
|
*(ulong UNALIGNED *)Mem = 0;
|
|
(ulong *)Mem += 1;
|
|
|
|
//
|
|
// Initialize the target and destination addresses.
|
|
//
|
|
((IPv6Addr *)Mem)[0] = TargetNCE->NeighborAddress;
|
|
((IPv6Addr *)Mem)[1] = *Destination;
|
|
(IPv6Addr *)Mem += 2;
|
|
|
|
if ((TargetLinkAddress != NULL) && (IF->WriteLLOpt != NULL)) {
|
|
void *TargetOption = Mem;
|
|
|
|
//
|
|
// Include a Target Link-Layer Address option.
|
|
//
|
|
((uchar *)TargetOption)[0] = ND_OPTION_TARGET_LINK_LAYER_ADDRESS;
|
|
((uchar *)TargetOption)[1] = TargetOptionLength >> 3;
|
|
|
|
(*IF->WriteLLOpt)(IF->LinkContext, TargetOption, TargetLinkAddress);
|
|
(uchar *)Mem += TargetOptionLength;
|
|
}
|
|
|
|
//
|
|
// Include a Redirected Header option.
|
|
//
|
|
((uchar *)Mem)[0] = ND_OPTION_REDIRECTED_HEADER;
|
|
((uchar *)Mem)[1] = RedirectOptionLength >> 3;
|
|
RtlZeroMemory(&((uchar *)Mem)[2], 6);
|
|
(uchar *)Mem += 8;
|
|
|
|
//
|
|
// Include as much of FwdPacket as will fit,
|
|
// zeroing any padding bytes.
|
|
//
|
|
NdisQueryPacket(FwdPacket, NULL, NULL, &FwdBuffer, NULL);
|
|
Ok = CopyNdisToFlat(Mem, FwdBuffer, FwdOffset, RedirectOptionData,
|
|
&FwdBuffer, &FwdOffset);
|
|
ASSERT(Ok);
|
|
(uchar *)Mem += RedirectOptionData;
|
|
RtlZeroMemory(Mem, RedirectOptionLength - (8 + RedirectOptionData));
|
|
|
|
//
|
|
// Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message
|
|
// starting with the ICMPv6 header, plus the IPv6 pseudo-header.
|
|
//
|
|
ICMP->Checksum = ChecksumPacket(
|
|
Packet, Offset + sizeof *IP, NULL, PayloadLength,
|
|
AlignAddr(&IP->Source), AlignAddr(&IP->Dest),
|
|
IP_PROTOCOL_ICMPv6);
|
|
ASSERT(ICMP->Checksum != 0);
|
|
|
|
//
|
|
// We prevent loopback of all ND packets.
|
|
//
|
|
PC(Packet)->Flags = NDIS_FLAGS_DONT_LOOPBACK;
|
|
|
|
//
|
|
// Send the Redirect packet.
|
|
//
|
|
IPv6SendND(Packet, Offset, NCE, Source);
|
|
}
|
|
|
|
//* RouterAdvertTimeout
|
|
//
|
|
// Called periodically from IPv6Timeout to handle timeouts
|
|
// for sending router advertisements for an interface.
|
|
// Our caller checks IF->RATimer and only calls us
|
|
// if there is a high probability that we need to send an RA.
|
|
//
|
|
// Callable from DPC context, not from thread context.
|
|
//
|
|
void
|
|
RouterAdvertTimeout(Interface *IF, int Force)
|
|
{
|
|
uint Now;
|
|
int SendRouterAdvert = FALSE;
|
|
NetTableEntry *NTE;
|
|
IPv6Addr Source;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
|
|
|
|
if (IF->RATimer != 0) {
|
|
ASSERT(!IsDisabledIF(IF));
|
|
ASSERT(IF->Flags & IF_FLAG_ADVERTISES);
|
|
|
|
if (Force) {
|
|
//
|
|
// Enter "fast" mode if this is a forced RA.
|
|
//
|
|
IF->RACount = MAX_INITIAL_RTR_ADVERTISEMENTS;
|
|
goto GenerateRA;
|
|
}
|
|
|
|
//
|
|
// Timer is running. Decrement and check for expiration.
|
|
//
|
|
if (--IF->RATimer == 0) {
|
|
GenerateRA:
|
|
//
|
|
// Timer went off. Check if rate-limiting
|
|
// prevents us from sending an RA right now.
|
|
//
|
|
Now = IPv6TickCount;
|
|
if ((uint)(Now - IF->RALast) < MIN_DELAY_BETWEEN_RAS) {
|
|
//
|
|
// We can not send an RA quite yet.
|
|
// Re-arm the timer.
|
|
//
|
|
IF->RATimer = MIN_DELAY_BETWEEN_RAS - (uint)(Now - IF->RALast);
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"RouterAdvertTimeout(IF %p): "
|
|
"rate limit\n", IF));
|
|
}
|
|
else {
|
|
//
|
|
// Re-arm the timer.
|
|
//
|
|
IF->RATimer = RandomNumber(MinRtrAdvInterval,
|
|
MaxRtrAdvInterval);
|
|
|
|
//
|
|
// Do we have an appropriate source address?
|
|
// During boot, the link-local address
|
|
// might still be tentative in which case we delay.
|
|
//
|
|
NTE = GetLinkLocalNTE(IF);
|
|
if (NTE == NULL) {
|
|
NTE = IF->LinkLocalNTE;
|
|
|
|
if ((NTE != NULL) && IsTentativeNTE(NTE)) {
|
|
//
|
|
// Try again when DAD finishes.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"RouterAdvertTimeout(IF %p): "
|
|
"wait for source\n", IF));
|
|
IF->RATimer = ((IF->RetransTimer * NTE->DADCount) +
|
|
NTE->DADTimer);
|
|
}
|
|
else {
|
|
//
|
|
// Skip this opportunity to send an RA.
|
|
//
|
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
|
"RouterAdvertTimeout(IF %p): "
|
|
"no source\n", IF));
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Generate a router advertisement.
|
|
//
|
|
SendRouterAdvert = TRUE;
|
|
Source = NTE->Address;
|
|
IF->RALast = Now;
|
|
|
|
//
|
|
// If we are in "fast" mode, then ensure
|
|
// that the next RA is sent quickly.
|
|
//
|
|
if (IF->RACount != 0) {
|
|
IF->RACount--;
|
|
if (IF->RATimer > MAX_INITIAL_RTR_ADVERT_INTERVAL)
|
|
IF->RATimer = MAX_INITIAL_RTR_ADVERT_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
|
|
|
|
if (SendRouterAdvert)
|
|
RouterAdvertSend(IF, &Source, &AllNodesOnLinkAddr);
|
|
}
|