Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5097 lines
149 KiB

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