// -*- 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 #include #include #include "alloca.h" #include "security.h" #include "mld.h" #include "md5.h" #include "info.h" #include 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); }