|
|
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// General IPv6 initialization code lives here.
// Actually, this file is mostly interface/address management code.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "llip6if.h"
#include "route.h"
#include "select.h"
#include "icmp.h"
#include "neighbor.h"
#include <tdiinfo.h>
#include <tdi.h>
#include <tdikrnl.h>
#include "alloca.h"
#include "security.h"
#include "mld.h"
#include "md5.h"
#include "info.h"
#include <ntddip6.h>
extern void TCPRemoveIF(Interface *IF); static void InterfaceStopForwarding(Interface *IF);
//
// Useful IPv6 Address Constants.
//
IPv6Addr UnspecifiedAddr = { 0 }; IPv6Addr LoopbackAddr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; IPv6Addr AllNodesOnNodeAddr = {0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; IPv6Addr AllNodesOnLinkAddr = {0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; IPv6Addr AllRoutersOnLinkAddr = {0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}; IPv6Addr LinkLocalPrefix = {0xfe, 0x80, }; IPv6Addr SiteLocalPrefix = {0xfe, 0xc0, }; IPv6Addr SixToFourPrefix = {0x20, 0x02, }; IPv6Addr V4MappedPrefix = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, }; IPv6Addr MulticastPrefix = {0xff, };
static uint MulticastScopes[] = { ADE_INTERFACE_LOCAL, ADE_LINK_LOCAL, ADE_SITE_LOCAL, ADE_ORG_LOCAL, ADE_GLOBAL };
//
// These variables are initialized from the registry.
// See ConfigureGlobalParameters.
//
uint DefaultCurHopLimit; uint MaxTempDADAttempts; uint MaxTempPreferredLifetime; uint MaxTempValidLifetime; uint TempRegenerateTime; uint UseTemporaryAddresses; uint MaxTempRandomTime; uint TempRandomTime;
#define TempPreferredLifetime (MaxTempPreferredLifetime - TempRandomTime)
//
// Timer variables.
//
KTIMER IPv6Timer; KDPC IPv6TimeoutDpc; int IPv6TimerStarted = FALSE;
uint PacketPoolSize;
NDIS_HANDLE IPv6PacketPool, IPv6BufferPool;
//
// Statistics
//
IPInternalPerCpuStats IPPerCpuStats[IPS_MAX_PROCESSOR_BUCKETS]; CACHE_ALIGN IPSNMPInfo IPSInfo; uint NumForwardingInterfaces;
//
// The NetTableListLock may be acquired while holding an interface lock.
//
NetTableEntry *NetTableList; // Global list of NTEs.
KSPIN_LOCK NetTableListLock; // Lock protecting this list.
//
// The IFListLock may be acquired while holding an interface lock
// or route lock.
//
KSPIN_LOCK IFListLock; // Lock protecting this list.
Interface *IFList = NULL; // List of interfaces active.
//
// The ZoneUpdateLock prevents concurrent updates
// of interface ZoneIndices.
//
KSPIN_LOCK ZoneUpdateLock;
//
// Used to assign indices to interfaces.
// See InterfaceIndex.
//
uint NextIFIndex = 0;
//* AddNTEToNetTableList
//
// Called with the list already locked.
//
void AddNTEToNetTableList(NetTableEntry *NTE) { if (NetTableList != NULL) NetTableList->PrevOnNTL = &NTE->NextOnNTL;
NTE->PrevOnNTL = &NetTableList; NTE->NextOnNTL = NetTableList; NetTableList = NTE; IPSInfo.ipsi_numaddr++; }
//* RemoveNTEFromNetTableList
//
// Called with the list already locked.
//
void RemoveNTEFromNetTableList(NetTableEntry *NTE) { NetTableEntry *NextNTE;
NextNTE = NTE->NextOnNTL; *NTE->PrevOnNTL = NextNTE; if (NextNTE != NULL) NextNTE->PrevOnNTL = NTE->PrevOnNTL; IPSInfo.ipsi_numaddr--; }
//* AddNTEToInterface
//
// Adds an NTE to an Interface's list of ADEs.
//
// Called with the interface already locked.
//
void AddNTEToInterface(Interface *IF, NetTableEntry *NTE) { //
// The NTE holds a reference for the interface,
// so anyone with a reference for the NTE
// can safely dereference NTE->IF.
//
AddRefIF(IF);
NTE->IF = IF; NTE->Next = IF->ADE; IF->ADE = (AddressEntry *)NTE; }
//* RemoveNTEFromInterface
//
// Removes a new NTE from the Interface's list of ADEs.
//
// Called with the interface already locked.
// The NTE must be first on the list.
//
void RemoveNTEFromInterface(Interface *IF, NetTableEntry *NTE) { ASSERT(IF->ADE == (AddressEntry *)NTE); IF->ADE = NTE->Next; ReleaseIF(IF); }
typedef struct SynchronizeMulticastContext { WORK_QUEUE_ITEM WQItem; Interface *IF; } SynchronizeMulticastContext;
//* SynchronizeMulticastAddresses
//
// Synchronize the interface's list of link-layer multicast addresses
// with the link's knowledge of those addresses.
//
// Callable from thread context, not from DPC context.
// Called with no locks held.
//
void SynchronizeMulticastAddresses(void *Context) { SynchronizeMulticastContext *smc = (SynchronizeMulticastContext *) Context; Interface *IF = smc->IF; void *LinkAddresses; LinkLayerMulticastAddress *MCastAddr; uint SizeofLLMA = SizeofLinkLayerMulticastAddress(IF); uint NumKeep, NumDeleted, NumAdded, Position; uint i; NDIS_STATUS Status; KIRQL OldIrql;
ExFreePool(smc);
//
// First acquire the heavy-weight lock used to serialize
// SetMCastAddrList operations.
//
KeWaitForSingleObject(&IF->WorkerLock, Executive, KernelMode, FALSE, NULL);
//
// Second acquire the lock that protects the interface,
// so we can examine IF->MCastAddresses et al.
//
KeAcquireSpinLock(&IF->Lock, &OldIrql);
//
// If this interface is going away, do nothing.
//
if (IsDisabledIF(IF)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "SynchronizeMulticastContext(IF %p)" " - disabled (%u refs)\n", IF, IF->RefCnt)); goto ErrorExit; }
//
// Allocate sufficient space for the link addresses
// that we will pass to SetMCastAddrList.
// This is actually an over-estimate.
//
LinkAddresses = ExAllocatePool(NonPagedPool, IF->MCastAddrNum * IF->LinkAddressLength); if (LinkAddresses == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "SynchronizeMulticastContext(IF %p) - no pool\n", IF)); goto ErrorExit; }
//
// Make three passes through the address array,
// constructing LinkAddresses.
//
NumKeep = 0; MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) {
if ((MCastAddr->RefCntAndFlags & LLMA_FLAG_REGISTERED) && IsLLMAReferenced(MCastAddr)) { //
// This address has already been registered,
// and we are keeping it.
//
Position = NumKeep++; RtlCopyMemory(((uchar *)LinkAddresses + Position * IF->LinkAddressLength), MCastAddr->LinkAddress, IF->LinkAddressLength); }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); }
if (NumKeep == IF->MCastAddrNum) { //
// Can happen if there are races between worker threads,
// but should be rare.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "SynchronizeMulticastAddresses - noop?\n")); ExFreePool(LinkAddresses); goto ErrorExit; }
NumAdded = 0; MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) {
if (!(MCastAddr->RefCntAndFlags & LLMA_FLAG_REGISTERED) && IsLLMAReferenced(MCastAddr)) { //
// This address has not been registered,
// and we are adding it.
// We set LLMA_FLAG_REGISTERED below,
// after we are past all error cases.
//
Position = NumKeep + NumAdded++; RtlCopyMemory(((uchar *)LinkAddresses + Position * IF->LinkAddressLength), MCastAddr->LinkAddress, IF->LinkAddressLength); }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); }
NumDeleted = 0; MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) {
if ((MCastAddr->RefCntAndFlags & LLMA_FLAG_REGISTERED) && !IsLLMAReferenced(MCastAddr)) { //
// This address has already been registered,
// and we are deleting it.
//
Position = NumKeep + NumAdded + NumDeleted++; RtlCopyMemory(((uchar *)LinkAddresses + Position * IF->LinkAddressLength), MCastAddr->LinkAddress, IF->LinkAddressLength); }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); }
//
// Some addresses might have been added & removed
// before being registered, so they have a zero RefCnt.
// We do not want to notify the link-layer about them.
//
ASSERT(NumKeep + NumAdded + NumDeleted <= IF->MCastAddrNum);
//
// Remove any unreferenced addresses.
//
if (NumKeep + NumAdded != IF->MCastAddrNum) { LinkLayerMulticastAddress *NewMCastAddresses; LinkLayerMulticastAddress *NewMCastAddr; LinkLayerMulticastAddress *MCastAddrMark; LinkLayerMulticastAddress *NextMCastAddr; UINT_PTR Length;
if (NumKeep + NumAdded == 0) { //
// None left.
//
NewMCastAddresses = NULL; } else { NewMCastAddresses = ExAllocatePool(NonPagedPool, ((NumKeep + NumAdded) * SizeofLLMA)); if (NewMCastAddresses == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "SynchronizeMulticastContext(IF %p)" " - no pool\n", IF)); ExFreePool(LinkAddresses); goto ErrorExit; }
//
// Copy the addresses that are still referenced
// to the new array. Normally there will only be
// one unreferenced address, so it's faster to search
// for it and then copy the elements before and after.
// Of course there might be multiple unreferenced addresses.
//
NewMCastAddr = NewMCastAddresses; MCastAddrMark = IF->MCastAddresses; for (i = 0, MCastAddr = IF->MCastAddresses; i < IF->MCastAddrNum; i++, MCastAddr = NextMCastAddr) {
NextMCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA);
if (!IsLLMAReferenced(MCastAddr)) { //
// Remove this address because it has no references.
//
if (MCastAddrMark < MCastAddr) { Length = (uchar *)MCastAddr - (uchar *)MCastAddrMark; RtlCopyMemory(NewMCastAddr, MCastAddrMark, Length); NewMCastAddr = (LinkLayerMulticastAddress *) ((uchar *)NewMCastAddr + Length); } MCastAddrMark = NextMCastAddr; } else { //
// Remember that we are registering this address.
//
MCastAddr->RefCntAndFlags |= LLMA_FLAG_REGISTERED; } }
if (MCastAddrMark < MCastAddr) { Length = (uchar *)MCastAddr - (uchar *)MCastAddrMark; RtlCopyMemory(NewMCastAddr, MCastAddrMark, Length); } }
ExFreePool(IF->MCastAddresses); IF->MCastAddresses = NewMCastAddresses; IF->MCastAddrNum = NumKeep + NumAdded; } else { //
// We need to set LLMA_FLAG_REGISTERED.
//
MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) {
MCastAddr->RefCntAndFlags |= LLMA_FLAG_REGISTERED;
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); } }
//
// We have constructed the LinkAddresses array from the interface.
// Before we can call SetMCastAddrList, we must drop the interface lock.
// We still hold the heavy-weight WorkerLock, so multiple SetMCastAddrList
// calls are properly serialized.
//
KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Pass the multicast link addresses down to the link layer,
// if there's actually anything changed.
//
if (NumAdded + NumDeleted == 0) { //
// Can happen if there are races between worker threads,
// but should be very rare.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "SynchronizeMulticastAddresses - noop?\n")); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "SynchronizeMulticastAddresses(IF %p) %u + %u + %u\n", IF, NumKeep, NumAdded, NumDeleted)); Status = (*IF->SetMCastAddrList)(IF->LinkContext, LinkAddresses, NumKeep, NumAdded, NumDeleted); if (Status != NDIS_STATUS_SUCCESS) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "SynchronizeMulticastAddresses(%p) -> %x\n", IF, Status)); } }
KeReleaseMutex(&IF->WorkerLock, FALSE); ExFreePool(LinkAddresses); ReleaseIF(IF); return;
ErrorExit: KeReleaseSpinLock(&IF->Lock, OldIrql); KeReleaseMutex(&IF->WorkerLock, FALSE); ReleaseIF(IF); }
//* DeferSynchronizeMulticastAddresses
//
// Because SynchronizeMulticastAddresses can only be called
// from a thread context with no locks held, this function
// provides a way to defer a call to SynchronizeMulticastAddresses
// when running at DPC level.
//
// In error cases (memory allocation failure),
// we return with IF_FLAG_MCAST_SYNC still set,
// so we will be called again later.
//
// Called with the interface lock held.
//
void DeferSynchronizeMulticastAddresses(Interface *IF) { SynchronizeMulticastContext *smc;
smc = ExAllocatePool(NonPagedPool, sizeof *smc); if (smc == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeferSynchronizeMulticastAddresses - no pool\n")); return; }
ExInitializeWorkItem(&smc->WQItem, SynchronizeMulticastAddresses, smc); smc->IF = IF; AddRefIF(IF); IF->Flags &= ~IF_FLAG_MCAST_SYNC;
ExQueueWorkItem(&smc->WQItem, CriticalWorkQueue); }
//* CheckLinkLayerMulticastAddress
//
// Is the interface receiving this link-layer multicast address?
//
// Callable from thread or DPC context.
// Called with no locks held.
//
int CheckLinkLayerMulticastAddress(Interface *IF, const void *LinkAddress) { if (IF->SetMCastAddrList == NULL) { //
// The interface does not track multicast link-layer addresses.
// For example, point-to-point or loopback interfaces.
// We must assume that the interface wants to receive all
// link-layer multicasts.
//
return TRUE; } else { KIRQL OldIrql; LinkLayerMulticastAddress *MCastAddr; uint SizeofLLMA = SizeofLinkLayerMulticastAddress(IF); uint i; int Found = FALSE;
KeAcquireSpinLock(&IF->Lock, &OldIrql); MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) { //
// Have we found the link-layer address?
//
if (RtlCompareMemory(MCastAddr->LinkAddress, LinkAddress, IF->LinkAddressLength) == IF->LinkAddressLength) { if (IsLLMAReferenced(MCastAddr)) Found = TRUE; break; }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); } KeReleaseSpinLock(&IF->Lock, OldIrql);
return Found; } }
//* AddLinkLayerMulticastAddress
//
// Called to indicate interest in the link-layer multicast address
// corresponding to the supplied IPv6 multicast address.
//
// Called with the interface locked.
//
void AddLinkLayerMulticastAddress(Interface *IF, const IPv6Addr *Address) { //
// If the interface doesn't keep track of link-layer multicast
// addresses (e.g., if it's P2P), we don't need to do anything.
//
if (IF->SetMCastAddrList != NULL) { void *LinkAddress = alloca(IF->LinkAddressLength); LinkLayerMulticastAddress *MCastAddr; uint SizeofLLMA = SizeofLinkLayerMulticastAddress(IF); uint i;
//
// Create the link-layer multicast address
// that corresponds to the IPv6 multicast address.
//
(*IF->ConvertAddr)(IF->LinkContext, Address, LinkAddress); //
// Check if the link-layer multicast address is already present.
//
MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) { //
// Have we found the link-layer address?
//
if (RtlCompareMemory(MCastAddr->LinkAddress, LinkAddress, IF->LinkAddressLength) == IF->LinkAddressLength) goto FoundMCastAddr; MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); } //
// We must add this link-layer multicast address.
//
MCastAddr = ExAllocatePool(NonPagedPool, (IF->MCastAddrNum + 1) * SizeofLLMA); if (MCastAddr == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "AddLinkLayerMulticastAddress - no pool\n")); return; } if (IF->MCastAddresses != NULL) { RtlCopyMemory(MCastAddr, IF->MCastAddresses, IF->MCastAddrNum * SizeofLLMA); ExFreePool(IF->MCastAddresses); }
IF->MCastAddresses = MCastAddr;
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + IF->MCastAddrNum * SizeofLLMA); MCastAddr->RefCntAndFlags = 0; RtlCopyMemory(MCastAddr->LinkAddress, LinkAddress, IF->LinkAddressLength);
IF->MCastAddrNum++; IF->Flags |= IF_FLAG_MCAST_SYNC;
FoundMCastAddr: AddRefLLMA(MCastAddr); } }
//* DelLinkLayerMulticastAddress
//
// Called to retract interest in the link-layer multicast address
// corresponding to the supplied IPv6 multicast address.
//
// Called with the interface locked.
//
void DelLinkLayerMulticastAddress(Interface *IF, IPv6Addr *Address) { //
// If the interface doesn't keep track of link-layer multicast
// addresses (e.g., if it's P2P), we don't need to do anything.
//
if (IF->SetMCastAddrList != NULL) { void *LinkAddress = alloca(IF->LinkAddressLength); LinkLayerMulticastAddress *MCastAddr; uint SizeofLLMA = SizeofLinkLayerMulticastAddress(IF); uint i;
//
// Create the link-layer multicast address
// that corresponds to the IPv6 multicast address.
//
(*IF->ConvertAddr)(IF->LinkContext, Address, LinkAddress);
//
// Find the link-layer multicast address.
// It must be present, but if it isn't, we avoid crashing.
//
MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) {
//
// Have we found the link-layer address?
//
if (RtlCompareMemory(MCastAddr->LinkAddress, LinkAddress, IF->LinkAddressLength) == IF->LinkAddressLength) { //
// Decrement the address's refcount.
// If it hits zero, indicate a need to synchronize.
//
ASSERT(IsLLMAReferenced(MCastAddr)); ReleaseLLMA(MCastAddr); if (!IsLLMAReferenced(MCastAddr)) IF->Flags |= IF_FLAG_MCAST_SYNC; break; }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); } ASSERT(i != IF->MCastAddrNum); } }
//* RestartLinkLayerMulticast
//
// Resets the status of link-layer multicast addresses,
// so that they are registered again with the link layer.
// The ResetDone function is called under a lock that serializes
// it with SetMCastAddrList calls.
//
// Callable from thread context, not DPC context.
//
void RestartLinkLayerMulticast( void *Context, void (*ResetDone)(void *Context)) { Interface *IF = (Interface *) Context; KIRQL OldIrql;
ASSERT(IF->SetMCastAddrList != NULL);
//
// Serialize with SetMCastAddrList operations.
//
KeWaitForSingleObject(&IF->WorkerLock, Executive, KernelMode, FALSE, NULL);
//
// So we can play with IF->MCastAddresses et al.
//
KeAcquireSpinLock(&IF->Lock, &OldIrql);
//
// If this interface is going away, do nothing.
//
if (IsDisabledIF(IF)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "RestartLinkLayerMulticast(IF %p)" " - disabled (%u refs)\n", IF, IF->RefCnt)); KeReleaseSpinLock(&IF->Lock, OldIrql); } else { LinkLayerMulticastAddress *MCastAddr; uint SizeofLLMA = SizeofLinkLayerMulticastAddress(IF); uint i;
//
// Reset the registered flag for all multicast addresses.
//
MCastAddr = IF->MCastAddresses; for (i = 0; i < IF->MCastAddrNum; i++) { if (IsLLMAReferenced(MCastAddr)) { MCastAddr->RefCntAndFlags &= ~LLMA_FLAG_REGISTERED; IF->Flags |= IF_FLAG_MCAST_SYNC; }
MCastAddr = (LinkLayerMulticastAddress *) ((uchar *)MCastAddr + SizeofLLMA); }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF); KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Let the link-layer know that the reset is done.
//
(*ResetDone)(IF->LinkContext); }
KeReleaseMutex(&IF->WorkerLock, FALSE); }
typedef enum { CONTROL_LOOPBACK_DISABLED, CONTROL_LOOPBACK_ENABLED, CONTROL_LOOPBACK_DESTROY } ControlLoopbackOp;
//* ControlLoopback
//
// Controls loopback functionality for a unicast or anycast address.
//
// This function is used in three ways, depending on Op:
// create a disabled loopback route (or disable an existing route),
// create an enabled loopback route (or enable an existing route),
// destroy any existing loopback route.
//
// It returns FALSE if there is a resource shortage.
// In actual usage, it will only fail when an NTE/AAE
// is first created, because subsequently the RTE and NCE
// will already exist.
//
// Called with the interface lock held.
//
int ControlLoopback(Interface *IF, const IPv6Addr *Address, ControlLoopbackOp Op) { NeighborCacheEntry *NCE; int Loopback; uint Lifetime; uint Type; int rc; NTSTATUS Status;
switch (Op) { case CONTROL_LOOPBACK_DISABLED: Loopback = FALSE; Lifetime = 0; Type = RTE_TYPE_SYSTEM; break;
case CONTROL_LOOPBACK_ENABLED: Loopback = TRUE; Lifetime = INFINITE_LIFETIME; Type = RTE_TYPE_SYSTEM; break;
case CONTROL_LOOPBACK_DESTROY: Loopback = FALSE; Lifetime = 0; Type = 0; // Special value for destroying system routes.
break;
default: ABORTMSG("ControlLoopback bad op"); return FALSE; }
//
// Get the NCE for this address.
//
NCE = FindOrCreateNeighbor(IF, Address); if (NCE == NULL) return FALSE;
//
// Update the loopback route for this address.
//
Status = RouteTableUpdate(NULL, // System update.
IF, NCE, Address, IPV6_ADDRESS_LENGTH, 0, Lifetime, Lifetime, ROUTE_PREF_LOOPBACK, Type, FALSE, FALSE); if (NT_SUCCESS(Status)) { //
// Update the address's loopback status in the neighbor cache.
//
ControlNeighborLoopback(NCE, Loopback); rc = TRUE; } else { //
// If RouteTableUpdate failed because the interface is
// being destroyed, then we succeed without doing anything.
//
rc = (Status == STATUS_INVALID_PARAMETER_1); }
ReleaseNCE(NCE); return rc; }
//* DeleteMAE
//
// Cleanup and delete an MAE because the multicast address
// is no longer assigned to the interface.
// It is already removed from the interface's list.
//
// Called with the interface already locked.
//
void DeleteMAE(Interface *IF, MulticastAddressEntry *MAE) { int SendDoneMsg;
KeAcquireSpinLockAtDpcLevel(&QueryListLock); if (!IsDisabledIF(IF) && (MAE->MCastFlags & MAE_LAST_REPORTER)) { //
// We need to send a Done message.
// Put the MAE on the QueryList with a zero timer.
//
if (MAE->MCastTimer == 0) AddToQueryList(MAE); else MAE->MCastTimer = 0; AddRefIF(IF); MAE->IF = IF;
SendDoneMsg = TRUE; } else { //
// If the MLD timer is running, remove from the query list.
//
if (MAE->MCastTimer != 0) RemoveFromQueryList(MAE);
SendDoneMsg = FALSE; } KeReleaseSpinLockFromDpcLevel(&QueryListLock);
//
// Retract our interest in the corresponding
// link-layer multicast address.
//
DelLinkLayerMulticastAddress(IF, &MAE->Address);
//
// Delete the MAE, unless we left it on the QueryList
// pending a Done message.
//
if (!SendDoneMsg) ExFreePool(MAE); }
//* FindAndReleaseMAE
//
// Finds the MAE for a multicast address and releases one reference
// for the MAE. May result in the MAE disappearing.
//
// If successful, returns the MAE.
// Note that it may be an invalid pointer!
// Returns NULL on failure.
//
// Called with the interface already locked.
//
MulticastAddressEntry * FindAndReleaseMAE(Interface *IF, const IPv6Addr *Addr) { AddressEntry **pADE; MulticastAddressEntry *MAE;
pADE = FindADE(IF, Addr); MAE = (MulticastAddressEntry *) *pADE; if (MAE != NULL) { if (MAE->Type == ADE_MULTICAST) { ASSERT(MAE->MCastRefCount != 0);
if (--MAE->MCastRefCount == 0) { //
// The MAE has no more references.
// Remove it from the Interface and delete it.
//
*pADE = MAE->Next; DeleteMAE(IF, MAE); } } else { //
// Return NULL for error.
//
MAE = NULL; } }
return MAE; }
//* FindAndReleaseSolicitedNodeMAE
//
// Finds the MAE for the corresponding solicited-node multicast address
// and releases one reference for the MAE.
// May result in the MAE disappearing.
//
// Called with the interface already locked.
//
void FindAndReleaseSolicitedNodeMAE(Interface *IF, const IPv6Addr *Addr) { if (IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) { IPv6Addr MCastAddr; MulticastAddressEntry *MAE;
//
// Create the corresponding solicited-node multicast address.
//
CreateSolicitedNodeMulticastAddress(Addr, &MCastAddr);
//
// Release the MAE for the solicited-node address.
// NB: This may fail during interface shutdown
// if we remove the solicited-node MAE before the NTE or AAE.
//
MAE = FindAndReleaseMAE(IF, &MCastAddr); ASSERT((MAE != NULL) || IsDisabledIF(IF)); } }
//* FindOrCreateMAE
//
// If an MAE for the multicast address already exists,
// just bump the reference count. Otherwise create a new MAE.
// Returns NULL for failure.
//
// If an NTE is supplied and an MAE is created,
// then the MAE is associated with the NTE.
//
// Called with the interface already locked.
//
MulticastAddressEntry * FindOrCreateMAE( Interface *IF, const IPv6Addr *Addr, NetTableEntry *NTE) { AddressEntry **pADE; MulticastAddressEntry *MAE;
//
// Can not create a new MAE if the interface is shutting down.
//
if (IsDisabledIF(IF)) return NULL;
pADE = FindADE(IF, Addr); MAE = (MulticastAddressEntry *) *pADE;
if (MAE == NULL) { //
// Create a new MAE.
//
MAE = ExAllocatePool(NonPagedPool, sizeof(MulticastAddressEntry)); if (MAE == NULL) return NULL;
//
// Initialize the new MAE.
//
if (NTE != NULL) MAE->NTE = NTE; else MAE->IF = IF; MAE->Address = *Addr; MAE->Type = ADE_MULTICAST; MAE->Scope = MulticastAddressScope(Addr); MAE->MCastRefCount = 0; // Incremented below.
MAE->MCastTimer = 0; MAE->NextQL = NULL;
//
// With any luck the compiler will optimize these
// field assignments...
//
if (IsMLDReportable(MAE)) { //
// We should send MLD reports for this address.
// Start by sending initial reports immediately.
//
MAE->MCastFlags = MAE_REPORTABLE; MAE->MCastCount = MLD_NUM_INITIAL_REPORTS; MAE->MCastTimer = 1; // Immediately.
KeAcquireSpinLockAtDpcLevel(&QueryListLock); AddToQueryList(MAE); KeReleaseSpinLockFromDpcLevel(&QueryListLock); } else { MAE->MCastFlags = 0; MAE->MCastCount = 0; MAE->MCastTimer = 0; }
//
// Add the MAE to the interface's ADE list.
//
MAE->Next = NULL; *pADE = (AddressEntry *)MAE;
//
// Indicate our interest in the corresponding
// link-layer multicast address.
//
AddLinkLayerMulticastAddress(IF, Addr); } else { ASSERT(MAE->Type == ADE_MULTICAST); }
MAE->MCastRefCount++; return MAE; }
//* FindOrCreateSolicitedNodeMAE
//
// Called with a unicast or anycast address.
//
// If an MAE for the solicited-node multicast address already exists,
// just bump the reference count. Otherwise create a new MAE.
// Returns TRUE for success.
//
// Called with the interface already locked.
//
int FindOrCreateSolicitedNodeMAE(Interface *IF, const IPv6Addr *Addr) { if (IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) { IPv6Addr MCastAddr;
//
// Create the corresponding solicited-node multicast address.
//
CreateSolicitedNodeMulticastAddress(Addr, &MCastAddr);
//
// Find or create an MAE for the solicited-node multicast address.
//
return FindOrCreateMAE(IF, &MCastAddr, NULL) != NULL; } else { //
// Only interfaces that support Neighbor Discovery
// use solicited-node multicast addresses.
//
return TRUE; } }
//* FindOrCreateAAE
//
// Adds an anycast address to the interface,
// associated with the NTE.
//
// If the interface already has the anycast address assigned,
// then this does nothing.
//
// Returns TRUE for success.
//
// Called with NO locks held.
// Callable from thread or DPC context.
//
int FindOrCreateAAE(Interface *IF, const IPv6Addr *Addr, NetTableEntryOrInterface *NTEorIF) { AddressEntry **pADE; AnycastAddressEntry *AAE; KIRQL OldIrql; int rc;
if (NTEorIF == NULL) NTEorIF = CastFromIF(IF);
KeAcquireSpinLock(&IF->Lock, &OldIrql); if (IsDisabledIF(IF)) { //
// Can't create a new AAE if the interface is shutting down.
//
rc = FALSE; } else { pADE = FindADE(IF, Addr); AAE = (AnycastAddressEntry *) *pADE; if (AAE == NULL) { //
// Create an AAE for the anycast address.
//
AAE = ExAllocatePool(NonPagedPool, sizeof(AnycastAddressEntry)); if (AAE == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "FindOrCreateAAE: no pool\n")); rc = FALSE; goto ErrorReturn; }
//
// Initialize the new AAE.
//
AAE->NTEorIF = NTEorIF; AAE->Address = *Addr; AAE->Type = ADE_ANYCAST; AAE->Scope = UnicastAddressScope(Addr);
//
// Add the AAE to the interface's ADE list.
// NB: FindOrCreateSolicitedNodeMAE may add an MAE at the end,
// so we do this first.
//
AAE->Next = NULL; *pADE = (AddressEntry *)AAE;
//
// Create the corresponding solicited-node
// multicast address MAE.
//
rc = FindOrCreateSolicitedNodeMAE(IF, Addr); if (! rc) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "FindOrCreateAAE: " "FindOrCreateSolicitedNodeMAE failed\n")); goto ErrorReturnFreeAAE; }
//
// Create a loopback route for this address.
//
rc = ControlLoopback(IF, Addr, CONTROL_LOOPBACK_ENABLED); if (! rc) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "FindOrCreateAAE: " "ControlLoopback failed\n")); FindAndReleaseSolicitedNodeMAE(IF, Addr);
ErrorReturnFreeAAE: //
// An MAE may have been added & removed above,
// but at this point the AAE should be last.
//
ASSERT((*pADE == (AddressEntry *)AAE) && (AAE->Next == NULL)); *pADE = NULL; ExFreePool(AAE);
ErrorReturn: ; } } else { //
// The ADE already exists -
// just verify that it is anycast.
//
rc = (AAE->Type == ADE_ANYCAST); }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF); } KeReleaseSpinLock(&IF->Lock, OldIrql);
return rc; }
//* DeleteAAE
//
// Cleanup and delete an AAE.
// It is already removed from the interface's list.
//
// Called with the interface lock held.
//
void DeleteAAE(Interface *IF, AnycastAddressEntry *AAE) { int rc;
//
// The corresponding solicited-node address is not needed.
//
FindAndReleaseSolicitedNodeMAE(IF, &AAE->Address);
//
// The loopback route is not needed.
//
rc = ControlLoopback(IF, &AAE->Address, CONTROL_LOOPBACK_DESTROY); ASSERT(rc);
ExFreePool(AAE); }
//* FindAndDeleteAAE
//
// Deletes an anycast address from the interface.
// Returns TRUE for success.
//
// Called with NO locks held.
// Callable from thread or DPC context.
//
int FindAndDeleteAAE(Interface *IF, const IPv6Addr *Addr) { AddressEntry **pADE; AnycastAddressEntry *AAE; KIRQL OldIrql; int rc;
KeAcquireSpinLock(&IF->Lock, &OldIrql);
pADE = FindADE(IF, Addr); AAE = (AnycastAddressEntry *) *pADE; if (AAE != NULL) { if (AAE->Type == ADE_ANYCAST) { //
// Delete the AAE.
//
*pADE = AAE->Next; DeleteAAE(IF, AAE); rc = TRUE; } else { //
// This is an error - it should be anycast.
//
rc = FALSE; } } else { //
// If the address already doesn't exist, then OK.
//
rc = TRUE; }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLock(&IF->Lock, OldIrql);
return rc; }
//* LeaveGroupAtAllScopes
//
// Leave a multicast group at all scopes.
// Called with the interface already locked.
//
void LeaveGroupAtAllScopes(Interface *IF, IPv6Addr *GroupAddr, uint MaxScope) { IPv6Addr Address = *GroupAddr; MulticastAddressEntry *MAE; uint i;
for (i = 0; ((i < sizeof MulticastScopes / sizeof MulticastScopes[0]) && (MulticastScopes[i] <= MaxScope)); i++) {
Address.s6_bytes[1] = (UCHAR)((Address.s6_bytes[1] & 0xf0) | MulticastScopes[i]); MAE = FindAndReleaseMAE(IF, &Address); ASSERT(MAE != NULL); } }
//* JoinGroupAtAllScopes
//
// Join a multicast group at all scopes up to the specified scope.
// Returns TRUE for success.
// Called with the interface already locked.
//
int JoinGroupAtAllScopes(Interface *IF, IPv6Addr *GroupAddr, uint MaxScope) { IPv6Addr Address = *GroupAddr; MulticastAddressEntry *MAE; uint i;
for (i = 0; ((i < sizeof MulticastScopes / sizeof MulticastScopes[0]) && (MulticastScopes[i] <= MaxScope)); i++) {
Address.s6_bytes[1] = (UCHAR)((Address.s6_bytes[1] & 0xf0) | MulticastScopes[i]); MAE = FindOrCreateMAE(IF, &Address, NULL); if (MAE == NULL) { //
// Failure. Leave the groups that we did manage to join.
//
if (i != 0) LeaveGroupAtAllScopes(IF, GroupAddr, MulticastScopes[i-1]); return FALSE; } }
return TRUE; }
//* DestroyADEs
//
// Destroy all AddressEntries that reference an NTE.
//
// Called with the interface already locked.
//
// (Actually, we are at DPC level because we hold the interface lock.)
//
void DestroyADEs(Interface *IF, NetTableEntry *NTE) { AddressEntry *AnycastList = NULL; AddressEntry *ADE, **PrevADE;
PrevADE = &IF->ADE; while ((ADE = *PrevADE) != NULL) { if (ADE == (AddressEntry *)NTE) { //
// Remove the NTE from the list but do not
// free the memory - that happens later.
//
*PrevADE = ADE->Next; } else if (ADE->NTE == NTE) { //
// Remove this ADE because it references the NTE.
//
*PrevADE = ADE->Next;
switch (ADE->Type) { case ADE_UNICAST: ABORTMSG("DestroyADEs: unicast ADE?\n"); break;
case ADE_ANYCAST: { //
// We can't call FindAndReleaseSolicitedNodeMAE here
// because it could mess up our list traversal.
// So put the ADE on our temporary list and do it later.
//
ADE->Next = AnycastList; AnycastList = ADE; break; }
case ADE_MULTICAST: { MulticastAddressEntry *MAE = (MulticastAddressEntry *) ADE;
DeleteMAE(IF, MAE); break; } } } else { if (ADE->Type == ADE_UNICAST) { TempNetTableEntry *TempNTE = (TempNetTableEntry *) ADE;
if ((TempNTE->AddrConf == ADDR_CONF_TEMPORARY) && (TempNTE->Public == NTE)) { //
// Break the public/temporary association
// and invalidate the temporary address.
// We can't use DestroyNTE directly here
// because it would mess up our traversal.
//
TempNTE->Public = NULL; TempNTE->ValidLifetime = 0; TempNTE->PreferredLifetime = 0; } }
PrevADE = &ADE->Next; } }
//
// Now we can safely process the anycast ADEs.
//
while ((ADE = AnycastList) != NULL) { AnycastList = ADE->Next; DeleteAAE(IF, (AnycastAddressEntry *)ADE); } }
//* FindADE - find an ADE entry for the given interface.
//
// If the address is assigned to the interface,
// returns the address of the link pointing to the ADE.
// Otherwise returns a pointer to the link (currently NULL)
// where a new ADE should be added to extend the list.
//
// The caller must lock the IF before calling this function.
//
AddressEntry ** FindADE( Interface *IF, const IPv6Addr *Addr) { AddressEntry **pADE, *ADE;
//
// Check if address is assigned to the interface using the
// interface's ADE list.
//
// REVIEW: Change the ADE list to a more efficient data structure?
//
for (pADE = &IF->ADE; (ADE = *pADE) != NULL; pADE = &ADE->Next) { if (IP6_ADDR_EQUAL(Addr, &ADE->Address)) break; }
return pADE; }
//* FindAddressOnInterface
//
// Looks for an ADE on the interface.
// If a unicast ADE is found, returns the ADE (an NTE) and ADE_UNICAST.
// If a multicast/anycast ADE is found, returns ADE->NTEorIF and ADE->Type.
// If an ADE is not found, returns the interface and ADE_NONE.
// Whether the interface or an NTE is returned,
// the return value (if non-NULL) holds a reference.
//
// Returns NULL only if the interface is disabled.
//
// In normal usage, callers should hold a reference
// for the interface. (So if the interface is returned,
// it is returned with a second reference.) But in some
// paths (for example IPv6Receive/IPv6HeaderReceive),
// the caller knows the interface exists but does not
// hold a reference for it.
//
// Callable from DPC context, not from thread context.
//
NetTableEntryOrInterface * FindAddressOnInterface( Interface *IF, const IPv6Addr *Addr, ushort *AddrType) { AddressEntry *ADE; NetTableEntryOrInterface *NTEorIF;
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
if (IsDisabledIF(IF)) {
NTEorIF = NULL; } else if ((ADE = *FindADE(IF, Addr)) != NULL) {
if ((*AddrType = ADE->Type) == ADE_UNICAST) { NTEorIF = CastFromNTE((NetTableEntry *)ADE); goto ReturnNTE; } else { NTEorIF = ADE->NTEorIF; if (IsNTE(NTEorIF)) ReturnNTE: AddRefNTE(CastToNTE(NTEorIF)); else goto ReturnIF; } } else {
*AddrType = ADE_NONE; NTEorIF = CastFromIF(IF);
ReturnIF: AddRefIF(CastToIF(NTEorIF)); }
KeReleaseSpinLockFromDpcLevel(&IF->Lock); return NTEorIF; }
//
// We keep track of the number of outstanding
// register-net-address work items.
// (Using InterlockedIncrement/InterlockedDecrement.)
// This way we can wait in the IPUnload
// until they are all done.
//
ULONG OutstandingRegisterNetAddressCount = 0;
//
// Note that this structure wouldn't be needed if IoQueueWorkItem
// had been designed to call the user's routine with the WorkItem
// as an additional argument along with the DeviceObject and Context.
// Sigh.
//
typedef struct RegisterNetAddressContext { PIO_WORKITEM WorkItem; NetTableEntry *NTE; } RegisterNetAddressContext;
//* RegisterNetAddressWorker - De/Registers an address with TDI.
//
// Worker function for calling TdiRegisterNetAddress.
//
// Called to register or deregister an address with TDI when any one of
// the following two events occur...
//
// 1. The corresponding NTE's DADState changes between valid/invalid
// states while its interface's media state is connected.
//
// 2. The corresponding NTE's interface media state changes between
// connected/disconnected while its DADState is DAD_STATE_PREFERRED.
// For this case, DisconnectADEs queues a worker on the connect to
// disconnect transition whereas on the reverse transition the worker
// is queued at the completion the duplicate address detection.
//
// Since TdiRegisterNetAddress must be called when running at
// IRQL < DISPATCH_LEVEL, we use this function via a worker thread.
//
// Called with a reference held on the NTE, which we release on exit.
//
void RegisterNetAddressWorker( PDEVICE_OBJECT DevObj, // Unused. Wish they passed the WorkItem instead.
PVOID Context) // A RegisterNetAddressContext struct.
{ RegisterNetAddressContext *MyContext = Context; NetTableEntry *NTE = MyContext->NTE; Interface *IF = NTE->IF; int ShouldBeRegistered; KIRQL OldIrql; NTSTATUS Status; uint ScopeId;
UNREFERENCED_PARAMETER(DevObj);
IoFreeWorkItem(MyContext->WorkItem); ExFreePool(MyContext);
//
// The heavy-weight WorkerLock protects this code against
// multiple instantiations of itself without raising IRQL.
//
KeWaitForSingleObject(&IF->WorkerLock, Executive, KernelMode, FALSE, NULL);
//
// Figure out what state we should be in.
// Note that IF->Lock protects DADState and IF->Flags,
// while IF->WorkerLock protects TdiRegistrationHandle.
//
KeAcquireSpinLock(&IF->Lock, &OldIrql); //
// An address should be registered with TDI iff it is in the
// preferred DAD state and its corresponding interface is
// connected.
//
ShouldBeRegistered = ((NTE->DADState == DAD_STATE_PREFERRED) && !(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)); KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// DetermineScopeId is correct, not IF->ZoneIndices[NTE->Scope]
// because we register "external" scopeids with TDI not internal
// scopeids.
//
ScopeId = DetermineScopeId(&NTE->Address, IF);
//
// We need to deregister the existing address if it shouldn't be
// registered any longer, or if we need to register a new address
// due to a scope id change.
//
if ((NTE->TdiRegistrationHandle != NULL) && (!ShouldBeRegistered || (NTE->TdiRegistrationScopeId != ScopeId))) {
Status = TdiDeregisterNetAddress(NTE->TdiRegistrationHandle); if (Status == STATUS_SUCCESS) {
NTE->TdiRegistrationHandle = NULL; } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RegisterNetAddressWorker: " "TdiDeregisterNetAddress(%d/%s): %x\n", IF->Index, FormatV6Address(&NTE->Address), Status));
//
// REVIEW: Should we requeue ourselves for another attempt?
//
} }
if (ShouldBeRegistered) { if (NTE->TdiRegistrationHandle == NULL) { char Buffer[sizeof(TA_ADDRESS) + TDI_ADDRESS_LENGTH_IP6 - 1]; PTA_ADDRESS TAAddress = (PTA_ADDRESS) Buffer; PTDI_ADDRESS_IP6 TDIAddress = (PTDI_ADDRESS_IP6) &TAAddress->Address;
//
// Create TAAddress from NTE->Address.
//
TAAddress->AddressLength = TDI_ADDRESS_LENGTH_IP6; TAAddress->AddressType = TDI_ADDRESS_TYPE_IP6; TDIAddress->sin6_port = 0; TDIAddress->sin6_flowinfo = 0; *(IPv6Addr *)&TDIAddress->sin6_addr = NTE->Address; TDIAddress->sin6_scope_id = ScopeId;
Status = TdiRegisterNetAddress(TAAddress, &IF->DeviceName, NULL, &NTE->TdiRegistrationHandle); if (Status == STATUS_SUCCESS) { NTE->TdiRegistrationScopeId = ScopeId; } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RegisterNetAddressWorker: " "TdiRegisterNetAddress(%d/%s): %x\n", IF->Index, FormatV6Address(&NTE->Address), Status));
//
// Due to a bug in TdiRegisterNetAddress, we can't be
// guaranteed the handle will be NULL on error.
//
NTE->TdiRegistrationHandle = NULL;
//
// REVIEW: Should we requeue ourselves for another attempt?
//
} } }
KeReleaseMutex(&IF->WorkerLock, FALSE); ReleaseNTE(NTE);
InterlockedDecrement((PLONG)&OutstandingRegisterNetAddressCount); }
//* DeferRegisterNetAddress
//
// Queue a work item that will execute RegisterNetAddressWorker.
//
// Callable from thread or DPC context.
//
void DeferRegisterNetAddress( NetTableEntry *NTE) // NTE that needs work.
{ RegisterNetAddressContext *Context; PIO_WORKITEM WorkItem;
Context = ExAllocatePool(NonPagedPool, sizeof *Context); if (Context == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeferRegisterNetAddress: ExAllocatePool failed\n")); return; }
WorkItem = IoAllocateWorkItem(IPDeviceObject); if (WorkItem == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeferRegisterNetAddress: IoAllocateWorkItem failed\n")); ExFreePool(Context); return; }
Context->WorkItem = WorkItem; AddRefNTE(NTE); Context->NTE = NTE;
InterlockedIncrement((PLONG)&OutstandingRegisterNetAddressCount);
IoQueueWorkItem(WorkItem, RegisterNetAddressWorker, CriticalWorkQueue, Context); }
//* AddrConfStartDAD
//
// Starts duplicate address detection for the address,
// unless DAD is disabled.
//
// Called with the interface locked.
//
void AddrConfStartDAD(Interface *IF, NetTableEntry *NTE) { if ((IF->DupAddrDetectTransmits == 0) || !(IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) || ((NTE->AddrConf == ADDR_CONF_TEMPORARY) && (MaxTempDADAttempts == 0))) {
//
// Duplicate Address Detection is disabled,
// so go straight to a valid state
// if we aren't already valid.
//
AddrConfNotDuplicate(IF, NTE); } else if (IF->Flags & IF_FLAG_MEDIA_DISCONNECTED) { //
// The interface is not connected,
// so we can not perform DAD.
// When the interface is connected,
// ReconnectADEs will start DAD.
//
} else { //
// Initialize for DAD.
// Send first solicit at next IPv6Timeout.
//
NTE->DADCount = (ushort)IF->DupAddrDetectTransmits; NTE->DADTimer = 1; } }
//* CreateNTE - Creates an NTE on an interface.
//
// Returns one reference for the caller.
//
// Callable from thread or DPC context.
// Called with the interface locked.
//
// (Actually, we are at DPC level because we hold the interface lock.)
//
NetTableEntry * CreateNTE(Interface *IF, const IPv6Addr *Address, uint AddrConf, uint ValidLifetime, uint PreferredLifetime) { uint Size; NetTableEntry *NTE = NULL;
//
// The address must not already be assigned.
//
ASSERT(*FindADE(IF, Address) == NULL);
//
// Can't create a new NTE if the interface is shutting down.
//
if (IsDisabledIF(IF)) goto ErrorExit;
//
// Temporary addresses need extra fields,
// which are initialized by our caller.
//
if (AddrConf == ADDR_CONF_TEMPORARY) Size = sizeof(TempNetTableEntry); else Size = sizeof(NetTableEntry);
NTE = ExAllocatePool(NonPagedPool, Size); if (NTE == NULL) goto ErrorExit;
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "CreateNTE(IF %u/%p, Addr %s) -> NTE %p\n", IF->Index, IF, FormatV6Address(Address), NTE));
//
// Initialize the NTE with one reference for our caller.
// (EnlivenNTE may add a second reference for the interface.)
//
RtlZeroMemory(NTE, Size); NTE->Address = *Address; NTE->Type = ADE_UNICAST; NTE->Scope = UnicastAddressScope(Address); AddNTEToInterface(IF, NTE); NTE->RefCnt = 1; NTE->AddrConf = (uchar)AddrConf; NTE->ValidLifetime = ValidLifetime; NTE->PreferredLifetime = PreferredLifetime; NTE->DADState = DAD_STATE_INVALID;
//
// Create a disabled loopback route.
// We pre-allocate the loopback RTE and NCE now,
// and then enable them later when the address is valid.
//
if (!ControlLoopback(IF, Address, CONTROL_LOOPBACK_DISABLED)) goto ErrorExitCleanup;
//
// Add this NTE to the front of the NetTableList.
//
KeAcquireSpinLockAtDpcLevel(&NetTableListLock); AddNTEToNetTableList(NTE); KeReleaseSpinLockFromDpcLevel(&NetTableListLock);
//
// If the NTE should be alive, make it so.
//
if (NTE->ValidLifetime != 0) EnlivenNTE(IF, NTE); return NTE;
ErrorExitCleanup: RemoveNTEFromInterface(IF, NTE); ASSERT(NTE->RefCnt == 1); ExFreePool(NTE);
ErrorExit: KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "CreateNTE(IF %u/%p, Addr %s) -> NTE %p failed\n", IF->Index, IF, FormatV6Address(Address), NTE)); return NULL; }
//* InterfaceIndex
//
// Allocates the next interface index.
//
uint InterfaceIndex(void) { return (uint) InterlockedIncrement((PLONG) &NextIFIndex); }
//* AddInterface
//
// Add a new interface to the global list.
//
void AddInterface(Interface *IF) { KIRQL OldIrql;
KeAcquireSpinLock(&IFListLock, &OldIrql); IF->Next = IFList; IFList = IF; IPSInfo.ipsi_numif++; KeReleaseSpinLock(&IFListLock, OldIrql); }
//* CreateGUIDFromName
//
// Given the string name of an interface, creates a corresponding guid.
// The guid is a hash of the string name.
//
void CreateGUIDFromName(const char *Name, GUID *Guid) { MD5_CTX Context;
MD5Init(&Context); MD5Update(&Context, (uchar *)Name, (uint)strlen(Name)); MD5Final(&Context); memcpy(Guid, Context.digest, MD5DIGESTLEN); }
//* CreateInterface
//
// Creates an IPv6 interface given some link-layer information.
// If successful, returns a reference for the interface.
//
// NB: With some NICs, NDIS will report that the adapter
// is disconnected after enabling the adapter. Then a second or two
// later, the adapter's status changes to connected.
// Presumably this has something to do with the miniport initialization,
// because the adapter is physically connected throughout.
// NDIS drops any packets that we send while the adapter is disconnected.
// So we must be careful that after the SetInterfaceLinkStatus, we (re)send
// the correct sequence of initialization packets (MLD, DAD, RA/RSs).
// To accomplish this, we postpone DAD while the interface is disconnected
// and we postpone sending RA/RSs. ReconnectADEs restarts MLD correctly
// for this situation so it does not need to be postponed.
//
// Callable from thread context, not DPC context.
//
// Return codes:
// STATUS_UNSUCCESSFUL
// STATUS_SUCCESS
//
NTSTATUS CreateInterface(const GUID *Guid, const LLIPv6BindInfo *BindInfo, void **Context) { UNICODE_STRING GuidName; Interface *IF = NULL; // Interface being added.
KIRQL OldIrql; uint IFSize; uint IFExportNamePrefixLen; NTSTATUS Status;
ASSERT(KeGetCurrentIrql() == 0); ASSERT(BindInfo->lip_addrlen <= MAX_LINK_LAYER_ADDRESS_LENGTH);
//
// Prevent new interfaces from being created
// while the stack is unloading.
//
if (Unloading) goto ErrorExit;
//
// Before doing the real work, take advantage of the link-layer
// address passed up here to re-seed our random number generator.
//
SeedRandom(BindInfo->lip_addr, BindInfo->lip_addrlen);
//
// Convert the guid to string form.
// It will be null-terminated.
//
Status = RtlStringFromGUID(Guid, &GuidName); if (! NT_SUCCESS(Status)) goto ErrorExit;
ASSERT(GuidName.MaximumLength == GuidName.Length + sizeof(WCHAR)); ASSERT(((WCHAR *)GuidName.Buffer)[GuidName.Length/sizeof(WCHAR)] == UNICODE_NULL);
//
// Allocate memory to hold an interface.
// We also allocate extra space to hold the device name string.
//
IFExportNamePrefixLen = sizeof IPV6_EXPORT_STRING_PREFIX - sizeof(WCHAR); IFSize = sizeof *IF + IFExportNamePrefixLen + GuidName.MaximumLength; IF = ExAllocatePool(NonPagedPool, IFSize); if (IF == NULL) goto ErrorExitCleanupGuidName;
RtlZeroMemory(IF, sizeof *IF); IF->IF = IF; IF->Index = InterfaceIndex(); IF->Guid = *Guid;
//
// Start with one reference because this is an active interface.
// And one reference for our caller.
//
IF->RefCnt = 2;
//
// Create the null-terminated exported device name from the guid.
//
IF->DeviceName.Buffer = (PVOID) (IF + 1); IF->DeviceName.MaximumLength = (USHORT) (IFSize - sizeof *IF); IF->DeviceName.Length = IF->DeviceName.MaximumLength - sizeof(WCHAR); RtlCopyMemory(IF->DeviceName.Buffer, IPV6_EXPORT_STRING_PREFIX, IFExportNamePrefixLen); RtlCopyMemory((uchar *) IF->DeviceName.Buffer + IFExportNamePrefixLen, GuidName.Buffer, GuidName.MaximumLength);
KeInitializeSpinLock(&IF->Lock);
IF->Type = BindInfo->lip_type; IF->Flags = (BindInfo->lip_flags & IF_FLAGS_BINDINFO);
if (BindInfo->lip_context == NULL) IF->LinkContext = IF; else IF->LinkContext = BindInfo->lip_context; IF->Transmit = BindInfo->lip_transmit; IF->CreateToken = BindInfo->lip_token; IF->ReadLLOpt = BindInfo->lip_rdllopt; IF->WriteLLOpt = BindInfo->lip_wrllopt; IF->ConvertAddr = BindInfo->lip_cvaddr; IF->SetRouterLLAddress = BindInfo->lip_setrtrlladdr; IF->SetMCastAddrList = BindInfo->lip_mclist; IF->Close = BindInfo->lip_close; IF->Cleanup = BindInfo->lip_cleanup; IF->LinkAddressLength = BindInfo->lip_addrlen; IF->LinkAddress = BindInfo->lip_addr; //
// We round-up the link-layer header size to a multiple of 2.
// This aligns the IPv6 header appropriately for IPv6Addr.
// When NDIS is fixed so we don't need AdjustPacketBuffer,
// we should align the IPv6 header to a multiple of 8.
//
IF->LinkHeaderSize = ALIGN_UP(BindInfo->lip_hdrsize, ushort);
IF->TrueLinkMTU = BindInfo->lip_maxmtu; IF->DefaultLinkMTU = BindInfo->lip_defmtu; IF->LinkMTU = BindInfo->lip_defmtu;
IF->DefaultPreference = BindInfo->lip_pref; IF->Preference = BindInfo->lip_pref; IF->BaseReachableTime = REACHABLE_TIME; IF->ReachableTime = CalcReachableTime(IF->BaseReachableTime); IF->RetransTimer = RETRANS_TIMER; IF->DefaultDupAddrDetectTransmits = BindInfo->lip_dadxmit; IF->DupAddrDetectTransmits = BindInfo->lip_dadxmit; IF->CurHopLimit = DefaultCurHopLimit; IF->DefSitePrefixLength = DEFAULT_SITE_PREFIX_LENGTH;
//
// Neighbor discovery requires multicast capability
//
ASSERT((IF->Flags & IF_FLAG_MULTICAST) || !(IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS));
//
// Router discovery requires either multicast capability,
// or a SetRouterLLAddress handler, or a Teredo interface.
//
ASSERT((IF->Flags & IF_FLAG_MULTICAST) || (IF->SetRouterLLAddress != NULL) || (IF->Type == IF_TYPE_TUNNEL_TEREDO) || !(IF->Flags & IF_FLAG_ROUTER_DISCOVERS));
//
// All interfaces are considered to be on different links
// but in the same site, until configured otherwise.
//
InitZoneIndices(IF->ZoneIndices, IF->Index);
NeighborCacheInit(IF);
//
// The worker lock serializes some heavy-weight
// calls to upper & lower layers.
//
KeInitializeMutex(&IF->WorkerLock, 0); //
// We need to get APCs while holding WorkerLock,
// so that we can get IO completions
// for our TDI calls on 6over4 interfaces.
// This is not a security problem because
// only kernel worker threads use WorkerLock
// so they can't be suspended by the user.
//
IF->WorkerLock.ApcDisable = 0;
//
// Initialize some random state for temporary addresses.
//
*(uint UNALIGNED *)&IF->TempState = Random();
//
// Register this interface's device name with TDI.
// We need to do this before assigning any unicast addresses to this IF,
// and also before grabbing the lock (thus setting IRQL to DISPATCH_LEVEL).
//
Status = TdiRegisterDeviceObject(&IF->DeviceName, &IF->TdiRegistrationHandle); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "CreateInterface(IF %u/%p): %ls -> %x\n", IF->Index, IF, IF->DeviceName.Buffer, Status)); if (Status != STATUS_SUCCESS) goto ErrorExitCleanupIF;
//
// After this point, we either return successfully
// or cleanup via ErrorExitDestroyIF.
//
RtlFreeUnicodeString(&GuidName);
//
// Return the new Interface to our caller now.
// This makes it available to the link-layer when
// we call CreateToken etc, before CreateInterface returns.
//
*Context = IF;
KeAcquireSpinLock(&IF->Lock, &OldIrql);
if (IF->Flags & IF_FLAG_ROUTER_DISCOVERS) { //
// Join the all-nodes multicast groups.
//
if (! JoinGroupAtAllScopes(IF, &AllNodesOnLinkAddr, ADE_LINK_LOCAL)) goto ErrorExitDestroyIF;
if (IF->Flags & IF_FLAG_ADVERTISES) { //
// Join the all-routers multicast groups.
//
if (! JoinGroupAtAllScopes(IF, &AllRoutersOnLinkAddr, ADE_SITE_LOCAL)) goto ErrorExitDestroyIF;
//
// Start sending Router Advertisements.
//
if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) IF->RATimer = 1; IF->RACount = MAX_INITIAL_RTR_ADVERTISEMENTS; } else { //
// Start sending Router Solicitations.
// The first RS will have the required random delay,
// because we randomize when IPv6Timeout first fires.
//
if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) IF->RSTimer = 1; } }
//
// Initialize RALast to a value safely in the past,
// so that when/if this interface first sends an RA
// it is not inhibited due to rate-limiting.
//
IF->RALast = IPv6TickCount - MIN_DELAY_BETWEEN_RAS;
if (IF->Flags & IF_FLAG_FORWARDS) InterlockedIncrement((PLONG)&NumForwardingInterfaces);
if (IF->CreateToken != NULL) { IPv6Addr Address; NetTableEntry *NTE;
//
// Create a link-local address for this interface.
// Other addresses will be created later via stateless
// auto-configuration.
//
Address = LinkLocalPrefix; (*IF->CreateToken)(IF->LinkContext, &Address);
NTE = CreateNTE(IF, &Address, ADDR_CONF_LINK, INFINITE_LIFETIME, INFINITE_LIFETIME); if (NTE == NULL) goto ErrorExitDestroyIF;
//
// The LinkLocalNTE field does not hold a reference.
//
IF->LinkLocalNTE = NTE; ReleaseNTE(NTE); }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Configure the interface from the registry.
//
ConfigureInterface(IF);
//
// Add ourselves to the front of the global interface list.
// This is done last so the interface is fully initialized
// when it shows up on the list.
//
AddInterface(IF);
//
// If the interface is multicast enabled, create a multicast route.
//
if (IF->Flags & IF_FLAG_MULTICAST) { RouteTableUpdate(NULL, // System update.
IF, NULL, &MulticastPrefix, 8, 0, INFINITE_LIFETIME, INFINITE_LIFETIME, ROUTE_PREF_ON_LINK, RTE_TYPE_SYSTEM, FALSE, FALSE); } return STATUS_SUCCESS;
ErrorExitDestroyIF: //
// Prevent calls down to the link layer,
// since our return code notifies the link layer
// synchronously that it should clean up.
//
IF->Close = NULL; IF->Cleanup = NULL; IF->SetMCastAddrList = NULL; KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Destroy the interface.
// This will cleanup addresses and routes.
// Then add the disabled interface to the list
// so InterfaceCleanup can find it after
// we release the last reference.
//
DestroyIF(IF); AddInterface(IF); ReleaseIF(IF); goto ErrorExit;
ErrorExitCleanupIF: //
// The interface has not been registered with TDI
// and there are no addresses, routes, etc.
// So we can just free it.
//
ASSERT(IF->RefCnt == 2); ExFreePool(IF);
ErrorExitCleanupGuidName: RtlFreeUnicodeString(&GuidName);
ErrorExit: KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "CreateInterface(IF %p) failed\n", IF)); return STATUS_UNSUCCESSFUL; }
//
// We keep track of the number of outstanding
// deregister-interface work items.
// (Using InterlockedIncrement/InterlockedDecrement.)
// This way we can wait in the IPUnload
// until they are all done.
//
ULONG OutstandingDeregisterInterfaceCount = 0;
//
// Note that this structure wouldn't be needed if IoQueueWorkItem
// had been designed to call the user's routine with the WorkItem
// as an additional argument along with the DeviceObject and Context.
// Sigh.
//
typedef struct DeregisterInterfaceContext { PIO_WORKITEM WorkItem; Interface *IF; } DeregisterInterfaceContext;
//* DeregisterInterfaceWorker - De/Registers an address with TDI.
//
// Worker function for calling TdiDeregisterDeviceObject.
// This is the last thing we do with the interface structure,
// so this routine also frees the interface.
// It has no references at this point.
//
void DeregisterInterfaceWorker( PDEVICE_OBJECT DevObj, // Unused. Wish they passed the WorkItem instead.
PVOID Context) // A DeregisterInterfaceContext struct.
{ DeregisterInterfaceContext *MyContext = Context; Interface *IF = MyContext->IF; NTSTATUS Status;
UNREFERENCED_PARAMETER(DevObj);
IoFreeWorkItem(MyContext->WorkItem); ExFreePool(MyContext);
//
// Deregister the interface with TDI, if it was registered.
// The loopback interface is not registered.
//
if (IF->TdiRegistrationHandle != NULL) { Status = TdiDeregisterDeviceObject(IF->TdiRegistrationHandle); if (Status != STATUS_SUCCESS) KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeregisterInterfaceContext: " "TdiDeregisterDeviceObject: %x\n", Status)); }
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "DeregisterInterfaceWorker(IF %u/%p) -> freed\n", IF->Index, IF));
//
// Perform final cleanup of the link-layer data structures.
//
if (IF->Cleanup != NULL) (*IF->Cleanup)(IF->LinkContext);
ExFreePool(IF);
//
// Note that we've finished our cleanup.
//
InterlockedDecrement((PLONG)&OutstandingDeregisterInterfaceCount); }
//* DeferDeregisterInterface
//
// Queue a work item that will execute DeregisterInterfaceWorker.
//
// Callable from thread or DPC context.
//
void DeferDeregisterInterface( Interface *IF) { DeregisterInterfaceContext *Context; PIO_WORKITEM WorkItem;
Context = ExAllocatePool(NonPagedPool, sizeof *Context); if (Context == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeferDeregisterInterface: ExAllocatePool failed\n")); return; }
WorkItem = IoAllocateWorkItem(IPDeviceObject); if (WorkItem == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "DeferDeregisterInterface: IoAllocateWorkItem failed\n")); ExFreePool(Context); return; }
Context->WorkItem = WorkItem; Context->IF = IF;
InterlockedIncrement((PLONG)&OutstandingDeregisterInterfaceCount);
IoQueueWorkItem(WorkItem, DeregisterInterfaceWorker, CriticalWorkQueue, Context); }
//* DestroyIF
//
// Shuts down an interface, making the interface effectively disappear.
// The interface will actually be freed when its last ref is gone.
//
// Callable from thread context, not DPC context.
// Called with NO locks held.
//
void DestroyIF(Interface *IF) { AddressEntry *ADE; int WasDisabled; KIRQL OldIrql;
//
// First things first: disable the interface.
// If it's already disabled, we're done.
//
KeAcquireSpinLock(&IF->Lock, &OldIrql); ASSERT(OldIrql == 0); KeAcquireSpinLockAtDpcLevel(&IFListLock); WasDisabled = IF->Flags & IF_FLAG_DISABLED; IF->Flags |= IF_FLAG_DISABLED; KeReleaseSpinLockFromDpcLevel(&IFListLock);
if (WasDisabled) { KeReleaseSpinLock(&IF->Lock, OldIrql); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "DestroyIF(IF %u/%p) - already disabled?\n", IF->Index, IF)); return; }
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "DestroyIF(IF %u/%p) -> disabled\n", IF->Index, IF));
//
// Stop generating Router Solicitations and Advertisements.
//
IF->RSTimer = IF->RATimer = 0;
//
// If the interface is currently forwarding,
// disable forwarding.
//
InterfaceStopForwarding(IF);
//
// Destroy all the ADEs. Because the interface is disabled,
// new ADEs will not subsequently be created.
//
while ((ADE = IF->ADE) != NULL) { //
// First, remove this ADE from the interface.
//
IF->ADE = ADE->Next;
switch (ADE->Type) { case ADE_UNICAST: { NetTableEntry *NTE = (NetTableEntry *) ADE; DestroyNTE(IF, NTE); break; }
case ADE_ANYCAST: { AnycastAddressEntry *AAE = (AnycastAddressEntry *) ADE; DeleteAAE(IF, AAE); break; }
case ADE_MULTICAST: { MulticastAddressEntry *MAE = (MulticastAddressEntry *) ADE; DeleteMAE(IF, MAE); break; } } } KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Shutdown the link-layer.
//
if (IF->Close != NULL) (*IF->Close)(IF->LinkContext);
//
// Clean up routing associated with the interface.
//
RouteTableRemove(IF);
//
// Clean up reassembly buffers associated with the interface.
//
ReassemblyRemove(IF);
//
// Clean up upper-layer state associated with the interface.
//
TCPRemoveIF(IF);
//
// Release the reference that the interface
// held for itself by virtue of being active.
//
ReleaseIF(IF);
//
// At this point, any NTEs still exist
// and hold references for the interface.
// The next calls to NetTableCleanup
// and InterfaceCleanup will finish the cleanup.
//
}
//* DestroyInterface
//
// Called from a link layer to destroy an interface.
//
// May be called when the interface has zero references
// and is already being destroyed.
//
void DestroyInterface(void *Context) { Interface *IF = (Interface *) Context;
DestroyIF(IF); }
//* ReleaseInterface
//
// Called from the link-layer to release its reference
// for the interface.
//
void ReleaseInterface(void *Context) { Interface *IF = (Interface *) Context;
ReleaseIF(IF); }
//* UpdateLinkMTU
//
// Update the link's MTU, either because of administrative configuration
// or autoconfiguration from a Router Advertisement.
//
// Callable from thread or DPC context.
// Called with NO locks held.
//
void UpdateLinkMTU(Interface *IF, uint MTU) { KIRQL OldIrql;
ASSERT((IPv6_MINIMUM_MTU <= MTU) && (MTU <= IF->TrueLinkMTU));
//
// If the interface is advertising, then it should
// send a new RA promptly because the RAs contain the MTU option.
// This is what really needs the lock and the IsDisabledIF check.
//
KeAcquireSpinLock(&IF->Lock, &OldIrql); if ((IF->LinkMTU != MTU) && !IsDisabledIF(IF)) { IF->LinkMTU = MTU; if (IF->Flags & IF_FLAG_ADVERTISES) { //
// Send a Router Advertisement very soon.
//
IF->RATimer = 1; } } KeReleaseSpinLock(&IF->Lock, OldIrql); }
//* FindInterfaceFromIndex
//
// Given the index of an interface, finds the interface.
// Returns a reference for the interface, or
// returns NULL if no valid interface is found.
//
// Callable from thread or DPC context.
//
Interface * FindInterfaceFromIndex(uint Index) { Interface *IF; KIRQL OldIrql;
KeAcquireSpinLock(&IFListLock, &OldIrql); for (IF = IFList; IF != NULL; IF = IF->Next) {
if (IF->Index == Index) { //
// Fail to find disabled interfaces.
//
if (IsDisabledIF(IF)) IF = NULL; else AddRefIF(IF); break; } } KeReleaseSpinLock(&IFListLock, OldIrql);
return IF; }
//* FindInterfaceFromGuid
//
// Given the guid of an interface, finds the interface.
// Returns a reference for the interface, or
// returns NULL if no valid interface is found.
//
// Callable from thread or DPC context.
//
Interface * FindInterfaceFromGuid(const GUID *Guid) { Interface *IF; KIRQL OldIrql;
KeAcquireSpinLock(&IFListLock, &OldIrql); for (IF = IFList; IF != NULL; IF = IF->Next) {
if (RtlCompareMemory(&IF->Guid, Guid, sizeof(GUID)) == sizeof(GUID)) { //
// Fail to find disabled interfaces.
//
if (IsDisabledIF(IF)) IF = NULL; else AddRefIF(IF); break; } } KeReleaseSpinLock(&IFListLock, OldIrql);
return IF; }
//* FindNextInterface
//
// Returns the next valid (not disabled) interface.
// If the argument is NULL, returns the first valid interface.
// Returns NULL if there is no next valid interface.
//
// Callable from thread or DPC context.
//
Interface * FindNextInterface(Interface *IF) { KIRQL OldIrql;
KeAcquireSpinLock(&IFListLock, &OldIrql);
if (IF == NULL) IF = IFList; else IF = IF->Next;
for (; IF != NULL; IF = IF->Next) { if (! IsDisabledIF(IF)) { AddRefIF(IF); break; } }
KeReleaseSpinLock(&IFListLock, OldIrql);
return IF; }
//* FindInterfaceFromZone
//
// Given a scope level and a zone index, finds an interface
// belonging to the specified zone. The interface
// must be different than the specified OrigIf.
//
// Called with the global ZoneUpdateLock lock held.
// (So we are at DPC level.)
//
Interface * FindInterfaceFromZone(Interface *OrigIF, uint Scope, uint Index) { Interface *IF;
KeAcquireSpinLockAtDpcLevel(&IFListLock); for (IF = IFList; IF != NULL; IF = IF->Next) {
if ((IF != OrigIF) && !IsDisabledIF(IF) && (IF->ZoneIndices[Scope] == Index)) {
AddRefIF(IF); break; } } KeReleaseSpinLockFromDpcLevel(&IFListLock);
return IF; }
//* FindNewZoneIndex
//
// This is a helper function for CheckZoneIndices.
//
// Given a scope level, finds an unused zone index
// for use at that scope level.
// We return the value one more than the largest
// value currently in use.
//
// Called with the global ZoneUpdateLock lock held.
// Called from DPC context.
//
uint FindNewZoneIndex(uint Scope) { Interface *IF; uint ZoneIndex = 1;
KeAcquireSpinLockAtDpcLevel(&IFListLock); for (IF = IFList; IF != NULL; IF = IF->Next) {
if (!IsDisabledIF(IF)) { if (ZoneIndex <= IF->ZoneIndices[Scope]) ZoneIndex = IF->ZoneIndices[Scope] + 1; } } KeReleaseSpinLockFromDpcLevel(&IFListLock);
return ZoneIndex; }
//* InitZoneIndices
//
// Initializes an array of zone indices to default values.
//
void InitZoneIndices( uint *ZoneIndices, uint Index) { ushort Scope;
ZoneIndices[ADE_SMALLEST_SCOPE] = Index; ZoneIndices[ADE_INTERFACE_LOCAL] = Index; ZoneIndices[ADE_LINK_LOCAL] = Index; for (Scope = ADE_LINK_LOCAL + 1; Scope <= ADE_LARGEST_SCOPE; Scope++) ZoneIndices[Scope] = 1; }
//* UpdateZoneIndices
//
// Helper function for updating zone indices on an interface.
//
// Called with the ZoneUpdateLock and the interface lock held.
//
void UpdateZoneIndices( Interface *IF, uint *ZoneIndices) { int SiteIdChanged, LinkIdChanged; AddressEntry *ADE; NetTableEntry *NTE;
LinkIdChanged = (ZoneIndices[ADE_LINK_LOCAL] != IF->ZoneIndices[ADE_LINK_LOCAL]); SiteIdChanged = (ZoneIndices[ADE_SITE_LOCAL] != IF->ZoneIndices[ADE_SITE_LOCAL]);
RtlCopyMemory(IF->ZoneIndices, ZoneIndices, sizeof IF->ZoneIndices);
//
// The following checks are just optimizations to avoid the for loop
// and to avoid unnecessarily calling RegisterNetAddressWorker.
//
if ((IF->Flags & IF_FLAG_MEDIA_DISCONNECTED) || (!LinkIdChanged && !SiteIdChanged)) return;
//
// Media is connected and the scope id has changed for unicast addresses.
// We need to inform TDI clients of the change in any relevant addresses.
//
for (ADE = IF->ADE; ADE != NULL; ADE = ADE->Next) { //
// Nothing to do for anycast or multicast addresses.
//
if (ADE->Type != ADE_UNICAST) continue;
if (((ADE->Scope == ADE_LINK_LOCAL) && LinkIdChanged) || ((ADE->Scope == ADE_SITE_LOCAL) && SiteIdChanged)) { NTE = (NetTableEntry *) ADE;
if (NTE->DADState == DAD_STATE_PREFERRED) { //
// Queue worker to tell TDI that this address has changed.
//
DeferRegisterNetAddress(NTE); } } } }
//* FindDefaultInterfaceForZone
//
// Given a scope level and a zone index, finds the default interface
// belonging to the specified zone. The default interface
// is the one that we assume destinations in the zone
// are on-link to, if there are no routes matching the destination.
//
// It is an error for the zone index to be zero, unless
// all our interfaces are in the same zone at that scope level.
// In which case zero (meaning unspecified) is actually not ambiguous.
//
// The default interface is returned as NULL upon failure,
// and with a reference upon success.
//
// Called with the route cache lock held.
// (So we are at DPC level.)
//
Interface * FindDefaultInterfaceForZone( uint Scope, uint ScopeId) { Interface *FirstIF = NULL; Interface *FoundIF = NULL; Interface *IF;
KeAcquireSpinLockAtDpcLevel(&IFListLock); for (IF = IFList; IF != NULL; IF = IF->Next) {
if (!IsDisabledIF(IF)) { if (ScopeId == 0) { //
// Do we have interfaces in two zones at this scope level?
//
if (FirstIF == NULL) {
FirstIF = IF; } else if (IF->ZoneIndices[Scope] != FirstIF->ZoneIndices[Scope]) { //
// Stop now with an error.
//
ASSERT(FoundIF != NULL); ReleaseIF(FoundIF); FoundIF = NULL; break; } }
//
// Can we potentially use this interface?
//
if ((ScopeId == 0) || (IF->ZoneIndices[Scope] == ScopeId)) {
if (FoundIF == NULL) { FoundInterface: AddRefIF(IF); FoundIF = IF; } else { //
// Is this new interface better than the previous one?
//
if (IF->Preference < FoundIF->Preference) { ReleaseIF(FoundIF); goto FoundInterface; } } } } } KeReleaseSpinLockFromDpcLevel(&IFListLock);
return FoundIF; }
#pragma BEGIN_INIT
//* IPInit - Initialize ourselves.
//
// This routine is called during initialization from the OS-specific
// init code.
//
int // Returns: 0 if initialization failed, non-zero if it succeeds.
IPInit(void) { NDIS_STATUS Status; LARGE_INTEGER Time; uint InitialWakeUp; uchar InitialRandomBits[16]; // This size was arbitrarily chosen.
ASSERT(ConvertSecondsToTicks(0) == 0); ASSERT(ConvertSecondsToTicks(INFINITE_LIFETIME) == INFINITE_LIFETIME); ASSERT(ConvertSecondsToTicks(1) == IPv6_TICKS_SECOND);
ASSERT(ConvertTicksToSeconds(0) == 0); ASSERT(ConvertTicksToSeconds(IPv6_TICKS_SECOND) == 1); ASSERT(ConvertTicksToSeconds(INFINITE_LIFETIME) == INFINITE_LIFETIME);
ASSERT(ConvertMillisToTicks(1000) == IPv6_TICKS_SECOND); ASSERT(ConvertMillisToTicks(1) > 0);
KeInitializeSpinLock(&NetTableListLock); KeInitializeSpinLock(&IFListLock); KeInitializeSpinLock(&ZoneUpdateLock);
//
// Perform initial seed of our psuedo-random number generator using
// 'random' bits from the KSecDD driver. KSecDD reportedly seeds itself
// with various system-unique values, which is exactly what we want
// (in order to avoid synchronicity issues between machines).
//
if (!GetSystemRandomBits(InitialRandomBits, sizeof(InitialRandomBits))) { return FALSE; } SeedRandom(InitialRandomBits, sizeof(InitialRandomBits));
//
// Prepare our periodic timer and its associated DPC object.
//
// When the timer expires, the IPv6Timeout deferred procedure
// call (DPC) is queued. Everything we need to do at some
// specific frequency is driven off of this routine.
//
// We wait to start the timer until all our datastructures have
// been initialized below.
//
KeInitializeDpc(&IPv6TimeoutDpc, IPv6Timeout, NULL); // No parameter.
KeInitializeTimer(&IPv6Timer);
// Initialize the ProtocolSwitchTable.
ProtoTabInit();
//
// Create Packet and Buffer pools for IPv6.
//
switch (MmQuerySystemSize()) { case MmSmallSystem: PacketPoolSize = SMALL_POOL; break; case MmMediumSystem: PacketPoolSize = MEDIUM_POOL; break; case MmLargeSystem: default: PacketPoolSize = LARGE_POOL; break; } NdisAllocatePacketPool(&Status, &IPv6PacketPool, PacketPoolSize, sizeof(Packet6Context)); if (Status != NDIS_STATUS_SUCCESS) return FALSE;
//
// Currently, the size we pass to NdisAllocateBufferPool is ignored.
//
NdisAllocateBufferPool(&Status, &IPv6BufferPool, PacketPoolSize); if (Status != NDIS_STATUS_SUCCESS) return FALSE;
ReassemblyInit();
ICMPv6Init();
if (!IPSecInit()) return FALSE;
//
// Start the routing module.
//
InitRouting(); InitSelect();
//
// Start the IPv6 timer.
// Our data structures should all be initialized now.
//
// Start the timer with an initial relative expiration time and
// also a recurring period. The initial expiration time is
// negative (to indicate a relative time), and in 100ns units, so
// we first have to do some conversions. The initial expiration
// time is randomized to help prevent synchronization between
// different machines.
//
InitialWakeUp = RandomNumber(0, IPv6_TIMEOUT * 10000); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "IPv6: InitialWakeUp = %u\n", InitialWakeUp)); Time.QuadPart = - (LONGLONG) InitialWakeUp; KeSetTimerEx(&IPv6Timer, Time, IPv6_TIMEOUT, &IPv6TimeoutDpc);
//
// First create the loopback interface,
// so it will be interface 1.
//
if (!LoopbackInit()) return FALSE; // Couldn't initialize loopback.
//
// Second create the tunnel interface,
// so it will be interface 2.
// This can also result in 6over4 interfaces.
//
if (!TunnelInit()) return FALSE; // Couldn't initialize tunneling.
//
// Finally initialize with ndis,
// so ethernet interfaces can be created.
//
if (!LanInit()) return FALSE; // Couldn't initialize with ndis.
return TRUE; }
#pragma END_INIT
//* IPUnload
//
// Called to shutdown the IP module in preparation
// for unloading the protocol stack.
//
void IPUnload(void) { Interface *IF; KIRQL OldIrql;
TdiDeregisterProvider(IPv6ProviderHandle);
//
// Stop the periodic timer.
//
KeCancelTimer(&IPv6Timer);
//
// Call each interface's close function.
// Note that interfaces might disappear while
// the interface list is unlocked,
// but new interfaces will not be created
// and the list does not get reordered.
//
KeAcquireSpinLock(&IFListLock, &OldIrql); for (IF = IFList; IF != NULL; IF = IF->Next) { AddRefIF(IF); KeReleaseSpinLock(&IFListLock, OldIrql);
DestroyIF(IF);
KeAcquireSpinLock(&IFListLock, &OldIrql); ReleaseIF(IF); } KeReleaseSpinLock(&IFListLock, OldIrql);
//
// DestroyIF/DestroyNTE spawned RegisterNetAddressWorker threads.
// Wait for them all to finish executing.
// This needs to be done before NetTableCleanup.
//
while (OutstandingRegisterNetAddressCount != 0) { LARGE_INTEGER Interval; Interval.QuadPart = -1; // Shortest possible relative wait.
KeDelayExecutionThread(KernelMode, FALSE, &Interval); }
//
// TunnelUnload needs to be after calling DestroyIF
// on all the interfaces and before InterfaceCleanup.
//
TunnelUnload();
NetTableCleanup(); InterfaceCleanup(); UnloadSelect(); UnloadRouting(); IPSecUnload(); ReassemblyUnload();
ASSERT(NumForwardingInterfaces == 0); ASSERT(IPSInfo.ipsi_numif == 0);
//
// InterfaceCleanup spawned DeregisterInterfaceWorker threads.
// Wait for them all to finish executing.
// Unfortunately, there is no good builtin synchronization primitive
// for this task. However, in practice because of the relative
// priorities of the threads involved, we almost never actually
// wait here. So this solution is quite efficient.
//
while (OutstandingDeregisterInterfaceCount != 0) { LARGE_INTEGER Interval; Interval.QuadPart = -1; // Shortest possible relative wait.
KeDelayExecutionThread(KernelMode, FALSE, &Interval); }
#if DBG
{ NetTableEntry *NTE;
for (NTE = NetTableList; NTE != NULL; NTE = NTE->NextOnNTL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Leaked NTE %p (IF %u/%p) Addr %s Refs %u\n", NTE, NTE->IF->Index, NTE->IF, FormatV6Address(&NTE->Address), NTE->RefCnt)); }
for (IF = IFList; IF != NULL; IF = IF->Next) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Leaked IF %u/%p Refs %u\n", IF->Index, IF, IF->RefCnt)); } } #endif // DBG
//
// We must wait until all the interfaces are completely cleaned up
// by DeregisterInterfaceWorker before freeing the packet pools.
// This is because Lan interfaces hold onto a packet (ai_tdpacket)
// that is freed in LanCleanupAdapter. NdisFreePacketPool
// blows away any packets that are still allocated so we can't call
// IPv6FreePacket after NdisFreePacketPool/NdisFreeBufferPool.
//
NdisFreePacketPool(IPv6PacketPool); NdisFreeBufferPool(IPv6BufferPool); }
//* GetLinkLocalNTE
//
// Returns the interface's link-local NTE (without a reference), or
// returns NULL if the interface does not have a valid link-local address.
//
// Called with the interface locked.
//
NetTableEntry * GetLinkLocalNTE(Interface *IF) { NetTableEntry *NTE;
NTE = IF->LinkLocalNTE; if ((NTE == NULL) || !IsValidNTE(NTE)) { //
// If we didn't find a valid NTE in the LinkLocalNTE field,
// search the ADE list and cache the first valid link-local NTE
// we find (if any).
//
for (NTE = (NetTableEntry *) IF->ADE; NTE != NULL; NTE = (NetTableEntry *) NTE->Next) {
if ((NTE->Type == ADE_UNICAST) && IsValidNTE(NTE) && IsLinkLocal(&NTE->Address)) { //
// Cache this NTE for future reference.
//
IF->LinkLocalNTE = NTE; break; } } }
return NTE; }
//* GetLinkLocalAddress
//
// Returns the interface's link-local address,
// if it is valid. Otherwise, returns
// the unspecified address.
//
// Callable from thread or DPC context.
//
// Returns FALSE if the link-local address is not valid.
//
int GetLinkLocalAddress( Interface *IF, // Interface for which to find an address.
IPv6Addr *Addr) // Where to return address found (or unspecified).
{ KIRQL OldIrql; NetTableEntry *NTE; int Status;
KeAcquireSpinLock(&IF->Lock, &OldIrql);
NTE = GetLinkLocalNTE(IF); Status = (NTE != NULL); if (Status) *Addr = NTE->Address; else *Addr = UnspecifiedAddr;
KeReleaseSpinLock(&IF->Lock, OldIrql); return Status; }
//* FindOrCreateNTE
//
// Find the specified unicast address.
// If it already exists, update it.
// If it doesn't exist, create it if the lifetime is non-zero.
//
// Returns TRUE for success.
//
// Called with NO locks held.
// Callable from thread or DPC context.
//
int FindOrCreateNTE( Interface *IF, const IPv6Addr *Addr, uint AddrConf, uint ValidLifetime, uint PreferredLifetime) { NetTableEntry *NTE; KIRQL OldIrql; int rc;
ASSERT(!IsMulticast(Addr) && !IsUnspecified(Addr) && (!IsLoopback(Addr) || (IF == LoopInterface))); ASSERT(PreferredLifetime <= ValidLifetime); ASSERT(AddrConf != ADDR_CONF_TEMPORARY);
KeAcquireSpinLock(&IF->Lock, &OldIrql); NTE = (NetTableEntry *) *FindADE(IF, Addr); if (NTE == NULL) { //
// There is no such address, so create it.
//
NTE = CreateNTE(IF, Addr, AddrConf, ValidLifetime, PreferredLifetime); if (NTE == NULL) { rc = FALSE; } else { ReleaseNTE(NTE); rc = TRUE; } } else if ((NTE->Type == ADE_UNICAST) && (NTE->AddrConf == AddrConf)) { //
// Update the address lifetimes.
// If we set the lifetime to zero, AddrConfTimeout will remove it.
// NB: We do not allow NTE->AddrConf to change.
//
NTE->ValidLifetime = ValidLifetime; NTE->PreferredLifetime = PreferredLifetime; rc = TRUE; } else { //
// We found the address, but we can't update it.
//
rc = FALSE; }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLock(&IF->Lock, OldIrql);
return rc; }
//* CreateTemporaryAddress
//
// Creates an temporary address for the interface.
//
// Called with the interface locked.
//
void CreateTemporaryAddress(Interface *IF, const IPv6Addr *Prefix, IPv6Addr *Addr) { uint Now = IPv6TickCount;
if (TempRandomTime == 0) { //
// We delay initializing TempRandomTime until it is needed.
// This way the random number generator has been initialized.
//
TempRandomTime = RandomNumber(0, MaxTempRandomTime); }
//
// First, update the state that we use if it is too old.
//
if ((IF->TempStateAge == 0) || (UseTemporaryAddresses == USE_TEMP_ALWAYS) || ((uint)(Now - IF->TempStateAge) >= (TempPreferredLifetime - TempRegenerateTime))) {
TryAgain: IF->TempStateAge = Now;
if (UseTemporaryAddresses == USE_TEMP_COUNTER) { //
// When testing, it's convenient to use interface identifiers
// that aren't actually random.
//
*(UINT UNALIGNED *)&IF->TempState.s6_bytes[12] = net_long(net_long(*(UINT UNALIGNED *)&IF->TempState.s6_bytes[12]) + 1); } else { MD5_CTX Context; //
// The high half of IF->TempState is our history value.
// The low half is the temporary interface identifier.
//
// Append the history value to the usual interface identifier,
// and calculate the MD5 digest of the resulting quantity.
// Note MD5 digests and IPv6 addresses are the both 16 bytes,
// while our history value and the interface identifer are 8 bytes.
//
(*IF->CreateToken)(IF->LinkContext, &IF->TempState); MD5Init(&Context); MD5Update(&Context, (uchar *)&IF->TempState, sizeof IF->TempState); MD5Final(&Context); memcpy((uchar *)&IF->TempState, Context.digest, MD5DIGESTLEN); }
//
// Clear the universal/local bit to indicate local significance.
//
IF->TempState.s6_bytes[8] &= ~0x2; }
RtlCopyMemory(&Addr->s6_bytes[0], Prefix, 8); RtlCopyMemory(&Addr->s6_bytes[8], &IF->TempState.s6_bytes[8], 8);
//
// Check that we haven't accidently generated
// a known anycast address format,
// or an existing address on the interface.
//
if (IsKnownAnycast(Addr) || (*FindADE(IF, Addr) != NULL)) goto TryAgain; }
//* AddrConfUpdate - Perform address auto-configuration.
//
// Called when we receive a valid Router Advertisement
// with a Prefix Information option that has the A (autonomous) bit set.
//
// Our caller is responsible for any sanity-checking of the prefix.
//
// Our caller is responsible for checking that the preferred lifetime
// is not greater than the valid lifetime.
//
// Will also optionally return an NTE, with a reference for the caller.
// This is done when a public address is created.
//
// Called with NO locks held.
// Callable from DPC context, not from thread context.
//
void AddrConfUpdate( Interface *IF, const IPv6Addr *Prefix, uint ValidLifetime, uint PreferredLifetime, int Authenticated, NetTableEntry **pNTE) { NetTableEntry *NTE; int Create = TRUE;
ASSERT(PreferredLifetime <= ValidLifetime);
KeAcquireSpinLockAtDpcLevel(&IF->Lock); //
// Scan the existing Net Table Entries.
// Note that some of the list elements
// are actually ADEs of other flavors.
//
for (NTE = (NetTableEntry *)IF->ADE; ; NTE = (NetTableEntry *)NTE->Next) {
if (NTE == NULL) { //
// No existing entry for this prefix.
// Create an entry if the lifetime is non-zero.
//
if (Create && (ValidLifetime != 0)) { IPv6Addr Addr;
//
// Auto-configure a new public address.
//
Addr = *Prefix; (*IF->CreateToken)(IF->LinkContext, &Addr);
NTE = (NetTableEntry *) *FindADE(IF, &Addr); if (NTE != NULL) { if (NTE->Type == ADE_UNICAST) { //
// Resurrect the address for our use.
//
ASSERT(NTE->DADState == DAD_STATE_INVALID); NTE->ValidLifetime = ValidLifetime; NTE->PreferredLifetime = PreferredLifetime;
//
// And return this NTE.
//
AddRefNTE(NTE); } else { //
// We can not create the public address.
//
NTE = NULL; break; } } else { //
// Create the public address, returning the new NTE.
//
NTE = CreateNTE(IF, &Addr, ADDR_CONF_PUBLIC, ValidLifetime, PreferredLifetime); }
//
// Auto-configure a new temporary address,
// if appropriate. Note that temporary addresses cannot
// be used on interfaces that do not support ND, since
// we have no way to resolve them to link-layer addresses.
//
if ((UseTemporaryAddresses != USE_TEMP_NO) && !IsSiteLocal(Prefix) && (IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) && (PreferredLifetime > TempRegenerateTime) && (NTE != NULL)) {
IPv6Addr TempAddr; uint TempValidLife; uint TempPreferredLife; TempNetTableEntry *TempNTE;
CreateTemporaryAddress(IF, Prefix, &TempAddr);
TempValidLife = MIN(ValidLifetime, MaxTempValidLifetime); TempPreferredLife = MIN(PreferredLifetime, TempPreferredLifetime);
TempNTE = (TempNetTableEntry *) CreateNTE(IF, &TempAddr, ADDR_CONF_TEMPORARY, TempValidLife, TempPreferredLife); if (TempNTE != NULL) { //
// Create the association between
// the temporary & public address.
//
TempNTE->Public = NTE;
//
// Initialize the special temporary creation time.
// This limits the temporary address's lifetimes.
//
TempNTE->CreationTime = IPv6TickCount;
ReleaseNTE((NetTableEntry *)TempNTE); } else { //
// Failure - destroy the public address.
//
DestroyNTE(IF, NTE); ReleaseNTE(NTE); NTE = NULL; } } } break; }
//
// Is this a unicast address matching the prefix?
//
if ((NTE->Type == ADE_UNICAST) && (NTE->DADState != DAD_STATE_INVALID) && HasPrefix(&NTE->Address, Prefix, IPV6_ADDRESS_LENGTH - IPV6_ID_LENGTH)) { //
// Reset the lifetimes of auto-configured addresses.
// NB: RFC 2462 says to reset DHCP addresses too,
// but I think that's wrong.
//
// Note that to prevent denial of service,
// we don't accept updates that lower the lifetime
// to small values.
//
// AddrConfTimeout (called from IPv6Timeout) handles
// the invalid & deprecated state transitions.
//
if (IsStatelessAutoConfNTE(NTE)) {
if ((ValidLifetime > PREFIX_LIFETIME_SAFETY) || (ValidLifetime > NTE->ValidLifetime) || Authenticated) NTE->ValidLifetime = ValidLifetime; else if (NTE->ValidLifetime <= PREFIX_LIFETIME_SAFETY) ; // ignore
else NTE->ValidLifetime = PREFIX_LIFETIME_SAFETY;
NTE->PreferredLifetime = PreferredLifetime;
//
// For temporary addresses, ensure that the lifetimes
// are not extended indefinitely.
//
if (NTE->AddrConf == ADDR_CONF_TEMPORARY) { TempNetTableEntry *TempNTE = (TempNetTableEntry *)NTE; uint Now = IPv6TickCount;
//
// Must be careful of overflows in these comparisons.
// (Eg, TempNTE->ValidLifetime might be INFINITE_LIFETIME.)
// N Now
// V TempNTE->ValidLifetime
// MV MaxTempValidLifetime
// C TempNTE->CreationTime
// We want to check
// N + V > C + MV
// Transform this to
// N - C > MV - V
// Then underflow of MV - V must be checked but
// N - C is not a problem because the tick count wraps.
//
if ((TempNTE->ValidLifetime > MaxTempValidLifetime) || (Now - TempNTE->CreationTime > MaxTempValidLifetime - TempNTE->ValidLifetime)) { //
// This temporary address is showing its age.
// Must curtail its valid lifetime.
//
if (MaxTempValidLifetime > Now - TempNTE->CreationTime) TempNTE->ValidLifetime = TempNTE->CreationTime + MaxTempValidLifetime - Now; else TempNTE->ValidLifetime = 0; }
if ((TempNTE->PreferredLifetime > TempPreferredLifetime) || (Now - TempNTE->CreationTime > TempPreferredLifetime - TempNTE->PreferredLifetime)) { //
// This temporary address is showing its age.
// Must curtail its preferred lifetime.
//
if (TempPreferredLifetime > Now - TempNTE->CreationTime) TempNTE->PreferredLifetime = TempNTE->CreationTime + TempPreferredLifetime - Now; else TempNTE->PreferredLifetime = 0; } }
//
// Maintain our invariant that the preferred lifetime
// is not larger than the valid lifetime.
//
if (NTE->ValidLifetime < NTE->PreferredLifetime) NTE->PreferredLifetime = NTE->ValidLifetime; }
if (NTE->ValidLifetime != 0) { //
// We found an existing address that matches the prefix,
// so inhibit auto-configuration of a new address.
//
Create = FALSE; } } }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
if (pNTE != NULL) *pNTE = NTE; else if (NTE != NULL) ReleaseNTE(NTE); }
//* AddrConfDuplicate
//
// Duplicate Address Detection has found
// that the NTE conflicts with some other node.
//
// Called with the interface locked.
// Callable from thread or DPC context.
//
void AddrConfDuplicate(Interface *IF, NetTableEntry *NTE) { int rc;
ASSERT(NTE->IF == IF);
if ((NTE->DADState != DAD_STATE_INVALID) && (NTE->DADState != DAD_STATE_DUPLICATE)) { IF->DupAddrDetects++;
if (IsValidNTE(NTE)) { if (NTE->DADState == DAD_STATE_PREFERRED) { //
// Queue worker to tell TDI that this address is going away.
//
if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { DeferRegisterNetAddress(NTE); } }
//
// This NTE is no longer available as a source address.
//
InvalidateRouteCache();
//
// Disable the loopback route for this address.
//
rc = ControlLoopback(IF, &NTE->Address, CONTROL_LOOPBACK_DISABLED); ASSERT(rc); }
NTE->DADState = DAD_STATE_DUPLICATE; NTE->DADTimer = 0;
if (NTE->AddrConf == ADDR_CONF_TEMPORARY) { NetTableEntry *Public;
//
// Make this address invalid so it will go away.
// NB: We still have a ref for the NTE via our caller.
//
DestroyNTE(IF, NTE);
//
// Should we create a new temporary address?
//
if ((UseTemporaryAddresses != USE_TEMP_NO) && ((Public = ((TempNetTableEntry *)NTE)->Public) != NULL) && (Public->PreferredLifetime > TempRegenerateTime) && (IF->DupAddrDetects < MaxTempDADAttempts)) {
IPv6Addr TempAddr; TempNetTableEntry *NewNTE; uint TempValidLife; uint TempPreferredLife;
//
// Generate a new temporary address,
// forcing the use of a new interface identifier.
//
IF->TempStateAge = 0; CreateTemporaryAddress(IF, &NTE->Address, &TempAddr);
TempValidLife = MIN(Public->ValidLifetime, MaxTempValidLifetime); TempPreferredLife = MIN(Public->PreferredLifetime, TempPreferredLifetime);
//
// Configure the new address.
//
NewNTE = (TempNetTableEntry *) CreateNTE(IF, &TempAddr, ADDR_CONF_TEMPORARY, TempValidLife, TempPreferredLife); if (NewNTE == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "AddrConfDuplicate: CreateNTE failed\n")); } else { NewNTE->Public = Public; NewNTE->CreationTime = IPv6TickCount; ReleaseNTE((NetTableEntry *)NewNTE); } } } } }
//* AddrConfNotDuplicate
//
// Duplicate Address Detection has NOT found
// a conflict with another node.
//
// Called with the interface locked.
// Callable from thread or DPC context.
//
void AddrConfNotDuplicate(Interface *IF, NetTableEntry *NTE) { int rc;
//
// The address has passed Duplicate Address Detection.
// Transition to a valid state.
//
if (! IsValidNTE(NTE)) { if (NTE->PreferredLifetime == 0) NTE->DADState = DAD_STATE_DEPRECATED; else NTE->DADState = DAD_STATE_PREFERRED;
//
// This NTE is now available as a source address.
//
InvalidateRouteCache();
//
// Enable the loopback route for this address.
//
rc = ControlLoopback(IF, &NTE->Address, CONTROL_LOOPBACK_ENABLED); ASSERT(rc); }
//
// DAD is also triggered through an interface disconnect to connect
// transition in which case the address is not registered with TDI
// even if it is in the preferred state. Hence we queue a worker to
// tell TDI about this address outside the "if (!IsValidNTE)" clause.
//
if ((NTE->DADState == DAD_STATE_PREFERRED) && !(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { DeferRegisterNetAddress(NTE); } }
//* AddrConfResetAutoConfig
//
// Resets the interface's auto-configured address lifetimes.
//
// Called with the interface locked.
// Callable from thread or DPC context.
//
void AddrConfResetAutoConfig(Interface *IF, uint MaxLifetime) { NetTableEntry *NTE;
for (NTE = (NetTableEntry *) IF->ADE; NTE != NULL; NTE = (NetTableEntry *) NTE->Next) {
//
// Is this an auto-configured unicast address?
//
if ((NTE->Type == ADE_UNICAST) && IsStatelessAutoConfNTE(NTE)) {
//
// Set the valid lifetime to a small value.
// If we don't get an RA soon, the address will expire.
//
if (NTE->ValidLifetime > MaxLifetime) NTE->ValidLifetime = MaxLifetime; if (NTE->PreferredLifetime > NTE->ValidLifetime) NTE->PreferredLifetime = NTE->ValidLifetime; } } }
//* ReconnectADEs
//
// Callable from thread or DPC context.
// Called with the interface locked.
//
// (Actually, we are at DPC level because we hold the interface lock.)
//
void ReconnectADEs(Interface *IF) { AddressEntry *ADE;
for (ADE = IF->ADE; ADE != NULL; ADE = ADE->Next) { switch (ADE->Type) { case ADE_UNICAST: { NetTableEntry *NTE = (NetTableEntry *) ADE;
if (NTE->DADState != DAD_STATE_INVALID) { //
// Restart Duplicate Address Detection,
// if it is enabled for this interface.
//
AddrConfStartDAD(IF, NTE); } break; }
case ADE_ANYCAST: //
// Nothing to do for anycast addresses.
//
break;
case ADE_MULTICAST: { MulticastAddressEntry *MAE = (MulticastAddressEntry *) ADE;
//
// Rejoin this multicast group,
// if it is reportable.
//
KeAcquireSpinLockAtDpcLevel(&QueryListLock); if (MAE->MCastFlags & MAE_REPORTABLE) { MAE->MCastCount = MLD_NUM_INITIAL_REPORTS; if (MAE->MCastTimer == 0) AddToQueryList(MAE); MAE->MCastTimer = 1; } KeReleaseSpinLockFromDpcLevel(&QueryListLock); break; } } } }
//* DisconnectADEs
//
// Callable from thread or DPC context.
// Called with the interface locked.
//
// (Actually, we are at DPC level because we hold the interface lock.)
//
void DisconnectADEs(Interface *IF) { AddressEntry *ADE;
ASSERT(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED);
for (ADE = IF->ADE; ADE != NULL; ADE = ADE->Next) { if (ADE->Type == ADE_UNICAST) { NetTableEntry *NTE = (NetTableEntry *) ADE;
if (NTE->DADState == DAD_STATE_PREFERRED) { //
// Queue worker to tell TDI that this address is going away.
//
DeferRegisterNetAddress(NTE); } }
//
// Nothing to do for anycast or multicast addresses.
//
} }
//* DestroyNTE
//
// Make an NTE be invalid, resulting in its eventual destruction.
//
// In the DestroyIF case, the NTE has already been removed
// from the interface. In other situations, that doesn't happen
// until later, when NetTableCleanup runs.
//
// Callable from thread or DPC context.
// Called with the interface locked.
//
// (Actually, we are at DPC level because we hold the interface lock.)
//
void DestroyNTE(Interface *IF, NetTableEntry *NTE) { int rc;
ASSERT(NTE->IF == IF);
if (NTE->DADState != DAD_STATE_INVALID) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "DestroyNTE(NTE %p, Addr %s) -> invalid\n", NTE, FormatV6Address(&NTE->Address)));
if (IsValidNTE(NTE)) { if (NTE->DADState == DAD_STATE_PREFERRED) { //
// Queue worker to tell TDI that this address is going away.
//
if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { DeferRegisterNetAddress(NTE); } }
//
// This NTE is no longer available as a source address.
//
InvalidateRouteCache();
//
// Disable the loopback route for this address.
//
rc = ControlLoopback(IF, &NTE->Address, CONTROL_LOOPBACK_DISABLED); ASSERT(rc); }
//
// Invalidate this address.
//
NTE->DADState = DAD_STATE_INVALID; NTE->DADTimer = 0;
//
// We have to set its lifetime to zero,
// or else AddrConfTimeout will attempt
// to resurrect this address.
//
NTE->ValidLifetime = 0; NTE->PreferredLifetime = 0;
//
// The corresponding solicited-node address is not needed.
//
FindAndReleaseSolicitedNodeMAE(IF, &NTE->Address);
if (NTE == IF->LinkLocalNTE) { //
// Unmark it as the primary link-local NTE.
// GetLinkLocalAddress will update LinkLocalNTE lazily.
//
IF->LinkLocalNTE = NULL; }
//
// Release the interface's reference for the NTE.
//
ReleaseNTE(NTE); } }
//* EnlivenNTE
//
// Make an NTE come alive, transitioning out of DAD_STATE_INVALID.
//
// Callable from thread or DPC context.
// Called with the interface locked.
//
void EnlivenNTE(Interface *IF, NetTableEntry *NTE) { ASSERT(NTE->DADState == DAD_STATE_INVALID); ASSERT(NTE->ValidLifetime != 0);
//
// The NTE needs a corresponding solicited-node MAE.
// If this fails, leave the address invalid and
// try again later.
//
if (FindOrCreateSolicitedNodeMAE(IF, &NTE->Address)) { //
// The interface needs a reference for the NTE,
// because we are enlivening it.
//
AddRefNTE(NTE);
//
// Start the address in the tentative state.
//
NTE->DADState = DAD_STATE_TENTATIVE;
//
// Start duplicate address detection.
//
AddrConfStartDAD(IF, NTE); } }
//* AddrConfTimeout - Perform valid/preferred lifetime expiration.
//
// Called periodically from NetTableTimeout on every NTE.
// As usual, caller must hold a reference for the NTE.
//
// Called with NO locks held.
// Callable from DPC context, not from thread context.
//
void AddrConfTimeout(NetTableEntry *NTE) { Interface *IF = NTE->IF; int QueueWorker = FALSE; NetTableEntry *Public;
ASSERT(NTE->Type == ADE_UNICAST);
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
if (NTE->ValidLifetime == 0) { //
// If the valid lifetime is zero, then the NTE should be invalid.
//
DestroyNTE(IF, NTE); } else { //
// If the valid lifetime is non-zero, then the NTE should be alive.
//
if (NTE->DADState == DAD_STATE_INVALID) EnlivenNTE(IF, NTE);
if (NTE->ValidLifetime != INFINITE_LIFETIME) NTE->ValidLifetime--; }
//
// Note that TempRegenerateTime might be zero.
// In which case it's important to only do this
// when transitioning from preferred to deprecated,
// NOT every time the preferred lifetime is zero.
//
if ((NTE->AddrConf == ADDR_CONF_TEMPORARY) && (NTE->DADState == DAD_STATE_PREFERRED) && (NTE->PreferredLifetime == TempRegenerateTime) && (IF->Flags & IF_FLAG_NEIGHBOR_DISCOVERS) && (UseTemporaryAddresses != USE_TEMP_NO) && ((Public = ((TempNetTableEntry *)NTE)->Public) != NULL) && (Public->PreferredLifetime > TempRegenerateTime)) {
IPv6Addr TempAddr; TempNetTableEntry *NewNTE; uint TempValidLife; uint TempPreferredLife;
//
// We will soon deprecate this temporary address,
// so create a new temporary address.
//
CreateTemporaryAddress(IF, &NTE->Address, &TempAddr);
TempValidLife = MIN(Public->ValidLifetime, MaxTempValidLifetime); TempPreferredLife = MIN(Public->PreferredLifetime, TempPreferredLifetime);
//
// Configure the new address.
//
NewNTE = (TempNetTableEntry *) CreateNTE(IF, &TempAddr, ADDR_CONF_TEMPORARY, TempValidLife, TempPreferredLife); if (NewNTE == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "AddrConfTimeout: CreateNTE failed\n")); } else { NewNTE->Public = Public; NewNTE->CreationTime = IPv6TickCount; ReleaseNTE((NetTableEntry *)NewNTE); } }
//
// If the preferred lifetime is zero, then the NTE should be deprecated.
//
if (NTE->PreferredLifetime == 0) { if (NTE->DADState == DAD_STATE_PREFERRED) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "AddrConfTimeout(NTE %p, Addr %s) -> deprecated\n", NTE, FormatV6Address(&NTE->Address)));
//
// Make this address be deprecated.
//
NTE->DADState = DAD_STATE_DEPRECATED; if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { QueueWorker = TRUE; } InvalidateRouteCache(); } } else { //
// If the address was deprecated, then it should be preferred.
// (AddrConfUpdate must have just increased the preferred lifetime.)
//
if (NTE->DADState == DAD_STATE_DEPRECATED) { NTE->DADState = DAD_STATE_PREFERRED; if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { QueueWorker = TRUE; } InvalidateRouteCache(); }
if (NTE->PreferredLifetime != INFINITE_LIFETIME) NTE->PreferredLifetime--; }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
if (QueueWorker) DeferRegisterNetAddress(NTE); }
//* NetTableCleanup
//
// Cleans up any NetTableEntries with zero references.
//
// Called with NO locks held.
// Callable from thread or DPC context.
//
void NetTableCleanup(void) { NetTableEntry *DestroyList = NULL; NetTableEntry *NTE, *NextNTE; Interface *IF; KIRQL OldIrql; int rc;
KeAcquireSpinLock(&NetTableListLock, &OldIrql);
for (NTE = NetTableList; NTE != NULL; NTE = NextNTE) { NextNTE = NTE->NextOnNTL;
if (NTE->RefCnt == 0) { ASSERT(NTE->DADState == DAD_STATE_INVALID);
//
// We want to destroy this NTE.
// We have to release the list lock
// before we can acquire the interface lock,
// but we need references to hold the NTEs.
//
AddRefNTE(NTE); if (NextNTE != NULL) AddRefNTE(NextNTE); KeReleaseSpinLock(&NetTableListLock, OldIrql);
IF = NTE->IF; KeAcquireSpinLock(&IF->Lock, &OldIrql); KeAcquireSpinLockAtDpcLevel(&NetTableListLock);
//
// Now that we have the appropriate locks.
// Is no one else using this NTE?
//
ReleaseNTE(NTE); if (NTE->RefCnt == 0) { //
// OK, we will destroy this NTE.
// First remove from the list.
//
RemoveNTEFromNetTableList(NTE);
//
// It is now safe to release the list lock,
// because the NTE is removed from the list.
// We continue to hold the interface lock,
// so nobody can find this NTE via the interface.
//
KeReleaseSpinLockFromDpcLevel(&NetTableListLock);
//
// Remove ADEs that reference this address.
// Note that this also removes from the interface's list,
// but does not free, the NTE itself.
// NB: In the case of DestroyIF, the ADEs are already
// removed from the interface and DestroyADEs does nothing.
//
DestroyADEs(IF, NTE);
//
// Release the loopback route.
//
rc = ControlLoopback(IF, &NTE->Address, CONTROL_LOOPBACK_DESTROY); ASSERT(rc);
KeReleaseSpinLock(&IF->Lock, OldIrql);
//
// Put this NTE on the destroy list.
//
NTE->NextOnNTL = DestroyList; DestroyList = NTE;
KeAcquireSpinLock(&NetTableListLock, &OldIrql); } else { // if (NTE->RefCnt != 0)
//
// We will not be destroying this NTE after all.
// Release the interface lock but keep the list lock.
//
KeReleaseSpinLockFromDpcLevel(&IF->Lock); }
//
// At this point, we have the list lock again
// so we can release our reference for NextNTE.
//
if (NextNTE != NULL) ReleaseNTE(NextNTE); } }
KeReleaseSpinLock(&NetTableListLock, OldIrql);
while (DestroyList != NULL) { NTE = DestroyList; DestroyList = NTE->NextOnNTL;
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "NetTableCleanup(NTE %p, Addr %s) -> destroyed\n", NTE, FormatV6Address(&NTE->Address)));
ReleaseIF(NTE->IF); ExFreePool(NTE); } }
//* NetTableTimeout
//
// Called periodically from IPv6Timeout.
//
// Called with NO locks held.
// Callable from DPC context, not from thread context.
//
void NetTableTimeout(void) { NetTableEntry *NTE; int SawZeroReferences = FALSE;
//
// Because new NTEs are only added at the head of the list,
// we can unlock the list during our traversal
// and know that the traversal will terminate properly.
//
KeAcquireSpinLockAtDpcLevel(&NetTableListLock); for (NTE = NetTableList; NTE != NULL; NTE = NTE->NextOnNTL) { AddRefNTE(NTE); KeReleaseSpinLockFromDpcLevel(&NetTableListLock);
//
// Check for Duplicate Address Detection timeout.
// The timer check here is only an optimization,
// because it is made without holding the appropriate lock.
//
if (NTE->DADTimer != 0) DADTimeout(NTE);
//
// Perform lifetime expiration.
//
AddrConfTimeout(NTE);
KeAcquireSpinLockAtDpcLevel(&NetTableListLock); ReleaseNTE(NTE);
//
// We assume that loads of RefCnt are atomic.
//
if (NTE->RefCnt == 0) SawZeroReferences = TRUE; } KeReleaseSpinLockFromDpcLevel(&NetTableListLock);
if (SawZeroReferences) NetTableCleanup(); }
//* InterfaceCleanup
//
// Cleans up any Interfaces with zero references.
//
// Called with NO locks held.
// Callable from thread or DPC context.
//
void InterfaceCleanup(void) { Interface *DestroyList = NULL; Interface *IF, **PrevIF; KIRQL OldIrql;
KeAcquireSpinLock(&IFListLock, &OldIrql);
PrevIF = &IFList; while ((IF = *PrevIF) != NULL) {
if (IF->RefCnt == 0) {
ASSERT(IsDisabledIF(IF)); *PrevIF = IF->Next; IF->Next = DestroyList; DestroyList = IF; IPSInfo.ipsi_numif--;
} else { PrevIF = &IF->Next; } }
KeReleaseSpinLock(&IFListLock, OldIrql);
while (DestroyList != NULL) { IF = DestroyList; DestroyList = IF->Next;
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "InterfaceCleanup(IF %u/%p) -> destroyed\n", IF->Index, IF));
//
// ADEs should already be destroyed.
// We just need to cleanup NCEs and free the interface.
//
ASSERT(IF->ADE == NULL); NeighborCacheDestroy(IF); if (IF->MCastAddresses != NULL) ExFreePool(IF->MCastAddresses); DeferDeregisterInterface(IF); } }
//* InterfaceTimeout
//
// Called periodically from IPv6Timeout.
//
// Called with NO locks held.
// Callable from DPC context, not from thread context.
//
void InterfaceTimeout(void) { static uint RecalcReachableTimer = 0; int RecalcReachable; int ForceRAs; Interface *IF; int SawZeroReferences = FALSE;
//
// Recalculate ReachableTime every few hours,
// even if no Router Advertisements are received.
//
if (RecalcReachableTimer == 0) { RecalcReachable = TRUE; RecalcReachableTimer = RECALC_REACHABLE_INTERVAL; } else { RecalcReachable = FALSE; RecalcReachableTimer--; }
//
// Grab the value of ForceRouterAdvertisements.
//
ForceRAs = InterlockedExchange((PLONG)&ForceRouterAdvertisements, FALSE);
//
// Because new interfaces are only added at the head of the list,
// we can unlock the list during our traversal
// and know that the traversal will terminate properly.
//
KeAcquireSpinLockAtDpcLevel(&IFListLock); for (IF = IFList; IF != NULL; IF = IF->Next) { //
// We should not do any processing on an interface
// that has zero references. As an even stronger condition,
// we avoid doing any timeout processing if the interface
// is being destroyed. Of course, the interface might be
// destroyed after we drop the interface list lock.
//
if (! IsDisabledIF(IF)) { AddRefIF(IF); KeReleaseSpinLockFromDpcLevel(&IFListLock);
//
// Handle per-neighbor timeouts.
//
NeighborCacheTimeout(IF);
//
// Handle router solicitations.
// The timer check here is only an optimization,
// because it is made without holding the appropriate lock.
//
if (IF->RSTimer != 0) RouterSolicitTimeout(IF);
//
// Handle router advertisements.
// The timer check here is only an optimization,
// because it is made without holding the appropriate lock.
//
if (IF->RATimer != 0) RouterAdvertTimeout(IF, ForceRAs);
//
// Recalculate the reachable time.
//
if (RecalcReachable) {
KeAcquireSpinLockAtDpcLevel(&IF->Lock); IF->ReachableTime = CalcReachableTime(IF->BaseReachableTime); KeReleaseSpinLockFromDpcLevel(&IF->Lock); }
KeAcquireSpinLockAtDpcLevel(&IFListLock); ReleaseIF(IF); }
//
// We assume that loads of RefCnt are atomic.
//
if (IF->RefCnt == 0) SawZeroReferences = TRUE; } KeReleaseSpinLockFromDpcLevel(&IFListLock);
if (SawZeroReferences) InterfaceCleanup(); }
//* InterfaceStartAdvertising
//
// If the interface is not currently advertising,
// makes it start advertising.
//
// Called with the interface locked.
// Caller must check whether the interface is disabled.
//
NTSTATUS InterfaceStartAdvertising(Interface *IF) { ASSERT(! IsDisabledIF(IF)); ASSERT(IF->Flags & IF_FLAG_ROUTER_DISCOVERS);
if (!(IF->Flags & IF_FLAG_ADVERTISES)) { //
// Join the all-routers multicast groups.
//
if (! JoinGroupAtAllScopes(IF, &AllRoutersOnLinkAddr, ADE_SITE_LOCAL)) return STATUS_INSUFFICIENT_RESOURCES;
//
// A non-advertising interface is now advertising.
//
IF->Flags |= IF_FLAG_ADVERTISES;
//
// The reconnecting state is not useful
// for advertising interfaces because
// the interface will not receive RAs.
//
IF->Flags &= ~IF_FLAG_MEDIA_RECONNECTED;
//
// Remove addresses & routes that were auto-configured
// from Router Advertisements. Advertising interfaces
// must be manually configured. Better to remove it
// now than let it time-out at some random time.
//
AddrConfResetAutoConfig(IF, 0); RouteTableResetAutoConfig(IF, 0); InterfaceResetAutoConfig(IF);
//
// Start sending Router Advertisements.
//
IF->RATimer = 1; // Send first RA very quickly.
IF->RACount = MAX_INITIAL_RTR_ADVERTISEMENTS;
//
// Stop sending Router Solicitations.
//
IF->RSTimer = 0; }
return STATUS_SUCCESS; }
//* InterfaceStopAdvertising
//
// If the interface is currently advertising,
// stops the advertising behavior.
//
// Called with the interface locked.
// Caller must check whether the interface is disabled.
//
void InterfaceStopAdvertising(Interface *IF) { ASSERT(! IsDisabledIF(IF));
if (IF->Flags & IF_FLAG_ADVERTISES) { //
// Leave the all-routers multicast group.
//
LeaveGroupAtAllScopes(IF, &AllRoutersOnLinkAddr, ADE_SITE_LOCAL);
//
// Stop sending Router Advertisements.
//
IF->Flags &= ~IF_FLAG_ADVERTISES; IF->RATimer = 0;
//
// Remove addresses that were auto-configured
// from our own Router Advertisements.
// We will pick up new address lifetimes
// from other router's Advertisements.
// If some other router is not advertising
// the prefixes that this router was advertising,
// better to remove the addresses now than
// let them time-out at some random time.
//
AddrConfResetAutoConfig(IF, 0);
//
// There shouldn't be any auto-configured routes,
// but RouteTableResetAutoConfig also handles site prefixes.
//
RouteTableResetAutoConfig(IF, 0);
//
// Restore interface parameters.
//
InterfaceResetAutoConfig(IF);
//
// Send Router Solicitations again.
//
IF->RSCount = 0; IF->RSTimer = 1; } }
//* InterfaceStartForwarding
//
// If the interface is not currently forwarding,
// makes it start forwarding.
//
// Called with the interface locked.
//
void InterfaceStartForwarding(Interface *IF) { if (!(IF->Flags & IF_FLAG_FORWARDS)) { //
// Any change in forwarding behavior requires InvalidRouteCache
// because FindNextHop uses IF_FLAG_FORWARDS. Also force the next RA
// for all advertising interfaces to be sent quickly,
// because their content might depend on forwarding behavior.
//
IF->Flags |= IF_FLAG_FORWARDS; InterlockedIncrement((PLONG)&NumForwardingInterfaces); InvalidateRouteCache(); ForceRouterAdvertisements = TRUE; } }
//* InterfaceStopForwarding
//
// If the interface is currently forwarding,
// stops the forwarding behavior.
//
// Called with the interface locked.
//
void InterfaceStopForwarding(Interface *IF) { if (IF->Flags & IF_FLAG_FORWARDS) { //
// Any change in forwarding behavior requires InvalidRouteCache
// because FindNextHop uses IF_FLAG_FORWARDS. Also force the next RA
// for all advertising interfaces to be sent quickly,
// because their content might depend on forwarding behavior.
//
IF->Flags &= ~IF_FLAG_FORWARDS; InterlockedDecrement((PLONG)&NumForwardingInterfaces); InvalidateRouteCache(); ForceRouterAdvertisements = TRUE; } }
//* AddrConfResetManualConfig
//
// Removes manually-configured addresses from the interface.
//
// Called with the interface already locked.
//
void AddrConfResetManualConfig(Interface *IF) { AddressEntry *AnycastList = NULL; AddressEntry *ADE, **PrevADE;
//
// We have to be careful about how we destroy addresses,
// because FindAndReleaseSolicitedNodeMAE will mess up our traversal.
//
PrevADE = &IF->ADE; while ((ADE = *PrevADE) != NULL) { //
// Is this a manually configured address?
//
switch (ADE->Type) { case ADE_UNICAST: { NetTableEntry *NTE = (NetTableEntry *) ADE;
if (NTE->AddrConf == ADDR_CONF_MANUAL) { //
// Let NetTableTimeout destroy the address.
//
NTE->ValidLifetime = 0; NTE->PreferredLifetime = 0; } break; }
case ADE_ANYCAST: //
// Most anycast addresses are manually configured.
// Subnet anycast addresses are the only exception.
// They are also the only anycast addresses
// which point to an NTE instead of the interface.
//
if (ADE->IF == IF) { //
// Remove the ADE from the interface list.
//
*PrevADE = ADE->Next;
//
// Put the ADE on our temporary list.
//
ADE->Next = AnycastList; AnycastList = ADE; continue; } break; }
PrevADE = &ADE->Next; }
//
// Now we can safely process the anycast ADEs.
//
while ((ADE = AnycastList) != NULL) { AnycastList = ADE->Next; DeleteAAE(IF, (AnycastAddressEntry *)ADE); } }
//* InterfaceResetAutoConfig
//
// Resets interface parameters that are auto-configured
// from Router Advertisements.
//
// Called with the interface already locked.
//
void InterfaceResetAutoConfig(Interface *IF) { IF->LinkMTU = IF->DefaultLinkMTU; if (IF->BaseReachableTime != REACHABLE_TIME) { IF->BaseReachableTime = REACHABLE_TIME; IF->ReachableTime = CalcReachableTime(IF->BaseReachableTime); } IF->RetransTimer = RETRANS_TIMER; IF->CurHopLimit = DefaultCurHopLimit; }
//* InterfaceResetManualConfig
//
// Resets the manual configuration of the interface.
// Does not remove manual routes on the interface.
//
// Called with ZoneUpdateLock held.
//
void InterfaceResetManualConfig(Interface *IF) { KeAcquireSpinLockAtDpcLevel(&IF->Lock); if (! IsDisabledIF(IF)) { uint ZoneIndices[ADE_NUM_SCOPES];
//
// Reset manually-configured interface parameters.
//
IF->LinkMTU = IF->DefaultLinkMTU; IF->Preference = IF->DefaultPreference; if (IF->BaseReachableTime != REACHABLE_TIME) { IF->BaseReachableTime = REACHABLE_TIME; IF->ReachableTime = CalcReachableTime(IF->BaseReachableTime); } IF->RetransTimer = RETRANS_TIMER; IF->DupAddrDetectTransmits = IF->DefaultDupAddrDetectTransmits; IF->CurHopLimit = DefaultCurHopLimit; IF->DefSitePrefixLength = DEFAULT_SITE_PREFIX_LENGTH;
//
// ZoneUpdateLock is held by our caller.
//
InitZoneIndices(ZoneIndices, IF->Index); UpdateZoneIndices(IF, ZoneIndices);
//
// Remove manually-configured addresses.
//
AddrConfResetManualConfig(IF);
//
// Stop advertising and forwarding,
// if either of those behaviors are enabled.
//
InterfaceStopAdvertising(IF); InterfaceStopForwarding(IF);
//
// Reset the firewall mode.
//
IF->Flags &= ~IF_FLAG_FIREWALL_ENABLED; } KeReleaseSpinLockFromDpcLevel(&IF->Lock); }
//* InterfaceReset
//
// Resets manual configuration for all interfaces.
// Tunnel interfaces are destroyed.
// Other interfaces have their attributes reset to default values.
// Manually-configured addresses are removed.
//
// The end result should be the same as if the machine
// had just booted without any persistent configuration.
//
// Called with no locks held.
//
void InterfaceReset(void) { Interface *IF; KIRQL OldIrql;
//
// Because new interfaces are only added at the head of the list,
// we can unlock the list during our traversals
// and know that the traversal will terminate properly.
//
//
// First destroy any manually configured tunnel interfaces.
//
KeAcquireSpinLock(&IFListLock, &OldIrql); for (IF = IFList; IF != NULL; IF = IF->Next) { //
// We should not do any processing (even just AddRefIF) on an interface
// that has zero references. As an even stronger condition,
// we avoid doing any processing if the interface
// is being destroyed. Of course, the interface might be
// destroyed after we drop the interface list lock.
//
if (! IsDisabledIF(IF)) { AddRefIF(IF); KeReleaseSpinLock(&IFListLock, OldIrql);
if ((IF->Type == IF_TYPE_TUNNEL_6OVER4) || (IF->Type == IF_TYPE_TUNNEL_V6V4)) { //
// Destroy the tunnel interface.
//
DestroyIF(IF); }
KeAcquireSpinLock(&IFListLock, &OldIrql); ReleaseIF(IF); } } KeReleaseSpinLock(&IFListLock, OldIrql);
//
// Now reset the remaining interfaces,
// while holding ZoneUpdateLock so
// InterfaceResetManualConfig can reset
// the zone indices consistently across the interfaces.
//
KeAcquireSpinLock(&ZoneUpdateLock, &OldIrql); KeAcquireSpinLockAtDpcLevel(&IFListLock); for (IF = IFList; IF != NULL; IF = IF->Next) { if (! IsDisabledIF(IF)) { AddRefIF(IF); KeReleaseSpinLockFromDpcLevel(&IFListLock);
//
// Reset the interface.
//
InterfaceResetManualConfig(IF);
KeAcquireSpinLockAtDpcLevel(&IFListLock); ReleaseIF(IF); } } KeReleaseSpinLockFromDpcLevel(&IFListLock); KeReleaseSpinLock(&ZoneUpdateLock, OldIrql); }
//* UpdateInterface
//
// Allows the forwarding & advertising attributes
// of an interface to be changed.
//
// Called with no locks held.
//
// Return codes:
// STATUS_INVALID_PARAMETER_1 Bad Interface.
// STATUS_INSUFFICIENT_RESOURCES
// STATUS_SUCCESS
//
NTSTATUS UpdateInterface( Interface *IF, int Advertises, int Forwards) { KIRQL OldIrql; NTSTATUS Status = STATUS_SUCCESS;
KeAcquireSpinLock(&IF->Lock, &OldIrql); if (IsDisabledIF(IF)) { //
// Do not update an interface that is being destroyed.
//
Status = STATUS_INVALID_PARAMETER_1; } else if (Advertises == -1) { //
// Do not change the Advertises attribute.
//
} else if (!(IF->Flags & IF_FLAG_ROUTER_DISCOVERS)) { //
// The Advertises attribute can only be controlled
// on interfaces that support Neighbor Discovery.
//
Status = STATUS_INVALID_PARAMETER_1; } else { //
// Control the advertising behavior of the interface.
//
if (Advertises) { //
// Become an advertising interfacing,
// if it is not already.
//
Status = InterfaceStartAdvertising(IF); } else { //
// Stop being an advertising interface,
// if it is currently advertising.
//
InterfaceStopAdvertising(IF); } }
//
// Control the forwarding behavior, if we haven't had an error.
//
if ((Status == STATUS_SUCCESS) && (Forwards != -1)) { if (Forwards) { //
// If the interface is not currently forwarding,
// enable forwarding.
//
InterfaceStartForwarding(IF); } else { //
// If the interface is currently forwarding,
// disable forwarding.
//
InterfaceStopForwarding(IF); } }
if (IsMCastSyncNeeded(IF)) DeferSynchronizeMulticastAddresses(IF);
KeReleaseSpinLock(&IF->Lock, OldIrql);
return Status; }
//* ReconnectInterface
//
// Reconnect the interface. Called when a media connect notification
// is received (SetInterfaceLinkStatus) or when processing a renew
// request by IOCTL_IPV6_UPDATE_INTERFACE (IoctlUpdateInterface).
//
// Called with the interface already locked.
//
void ReconnectInterface( Interface *IF) { ASSERT(!IsDisabledIF(IF) && !(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED));
//
// Purge potentially obsolete link-layer information.
// Things might have changed while we were unplugged.
//
NeighborCacheFlush(IF, NULL); //
// Rejoin multicast groups and restart Duplicate Address Detection.
//
// Preferred unicast addresses are registered with TDI when
// duplicate address detection completes (or is disabled).
//
ReconnectADEs(IF);
if (IF->Flags & IF_FLAG_ROUTER_DISCOVERS) { if (IF->Flags & IF_FLAG_ADVERTISES) { //
// Send a Router Advertisement very soon.
//
IF->RATimer = 1; } else { //
// Start sending Router Solicitations.
//
IF->RSCount = 0; IF->RSTimer = 1;
//
// Remember that this interface was just reconnected,
// so when we receive a Router Advertisement
// we can take special action.
//
IF->Flags |= IF_FLAG_MEDIA_RECONNECTED; } } //
// We might have moved to a new link.
// Force the generation of a new temporary interface identifier.
// This only really makes a difference if we generate
// new addresses on this link - if it's the same link then
// we continue to use our old addresses, both public & temporary.
//
IF->TempStateAge = 0; }
//* DisconnectInterface
//
// Disconnect the interface. Called when a media disconnect
// notification is received (SetInterfaceLinkStatus) for a connected
// interface.
//
// Called with the interface already locked.
//
void DisconnectInterface( Interface *IF) { ASSERT(!IsDisabledIF(IF) && (IF->Flags & IF_FLAG_MEDIA_DISCONNECTED));
//
// Deregister any preferred unicast addresses from TDI.
//
DisconnectADEs(IF); }
//* SetInterfaceLinkStatus
//
// Change the interface's link status. In particular,
// set whether the media is connected or disconnected.
//
// May be called when the interface has zero references
// and is already being destroyed.
//
void SetInterfaceLinkStatus( void *Context, int MediaConnected) // TRUE or FALSE.
{ Interface *IF = (Interface *) Context; KIRQL OldIrql;
//
// Note that media-connect/disconnect events
// can be "lost". We are not informed if the
// cable is unplugged/replugged while we are
// shutdown, hibernating, or on standby.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "SetInterfaceLinkStatus(IF %p) -> %s\n", IF, MediaConnected ? "connected" : "disconnected"));
KeAcquireSpinLock(&IF->Lock, &OldIrql);
if (! IsDisabledIF(IF)) { if (MediaConnected) { if (IF->Flags & IF_FLAG_MEDIA_DISCONNECTED) { //
// The cable was plugged back in.
//
IF->Flags &= ~IF_FLAG_MEDIA_DISCONNECTED;
//
// Changes in IF_FLAG_MEDIA_DISCONNECTED must
// invalidate the route cache.
//
InvalidateRouteCache(); }
//
// A connect event implies a change in the interface state
// regardless of whether the interface is already connected.
// Hence we process it outside the 'if' clause.
//
ReconnectInterface(IF); } else { if (!(IF->Flags & IF_FLAG_MEDIA_DISCONNECTED)) { //
// The cable was unplugged.
//
IF->Flags = (IF->Flags | IF_FLAG_MEDIA_DISCONNECTED) &~ IF_FLAG_MEDIA_RECONNECTED;
//
// Changes in IF_FLAG_MEDIA_DISCONNECTED must
// invalidate the route cache.
//
InvalidateRouteCache();
//
// A disconnect event implies a change in the interface
// state only if the interface is already connected.
// Hence we process it inside the 'if' clause.
//
DisconnectInterface(IF); } } }
KeReleaseSpinLock(&IF->Lock, OldIrql); }
|