// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs) // // Copyright (c) 1998-2000 Microsoft Corporation // // This file is part of the Microsoft Research IPv6 Network Protocol Stack. // You should have received a copy of the Microsoft End-User License Agreement // for this software along with this release; see the file "license.txt". // If not, please see http://www.research.microsoft.com/msripv6/license.htm, // or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399. // // Abstract: // // Routing routines for Internet Protocol Version 6. // #include "oscfg.h" #include "ndis.h" #include "ip6imp.h" #include "ip6def.h" #include "route.h" #include "select.h" #include "icmp.h" #include "neighbor.h" #include "alloca.h" #include "ipinfo.h" #include "info.h" // // Forward declarations of internal functions. // extern void DestroyBCE(BindingCacheEntry *BCE); KSPIN_LOCK RouteCacheLock; KSPIN_LOCK RouteTableLock; struct RouteCache RouteCache; struct RouteTable RouteTable; ulong RouteCacheValidationCounter; struct BindingCache BindingCache; SitePrefixEntry *SitePrefixTable = NULL; LIST_ENTRY RouteNotifyQueue; int ForceRouterAdvertisements = FALSE; //* RemoveRTE // // Remove the RTE from the route table. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void RemoveRTE(RouteTableEntry **PrevRTE, RouteTableEntry *RTE) { ASSERT(*RouteTable.Last == NULL); ASSERT(*PrevRTE == RTE); *PrevRTE = RTE->Next; if (RouteTable.Last == &RTE->Next) RouteTable.Last = PrevRTE; } //* InsertRTEAtFront // // Insert the RTE at the front of the route table. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void InsertRTEAtFront(RouteTableEntry *RTE) { ASSERT(*RouteTable.Last == NULL); RTE->Next = RouteTable.First; RouteTable.First = RTE; if (RouteTable.Last == &RouteTable.First) RouteTable.Last = &RTE->Next; } //* InsertRTEAtBack // // Insert the RTE at the back of the route table. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void InsertRTEAtBack(RouteTableEntry *RTE) { ASSERT(*RouteTable.Last == NULL); RTE->Next = NULL; *RouteTable.Last = RTE; RouteTable.Last = &RTE->Next; if (RouteTable.First == NULL) RouteTable.First = RTE; } //* InsertRCE // // Insert the RCE in the route cache. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void InsertRCE(RouteCacheEntry *RCE) { RouteCacheEntry *AfterRCE = SentinelRCE; RCE->Prev = AfterRCE; (RCE->Next = AfterRCE->Next)->Prev = RCE; AfterRCE->Next = RCE; RouteCache.Count++; } //* RemoveRCE // // Remove the RCE from the route cache. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void RemoveRCE(RouteCacheEntry *RCE) { RCE->Prev->Next = RCE->Next; RCE->Next->Prev = RCE->Prev; RouteCache.Count--; // // We must ensure that an RCE not in the route cache // has a null BCE. This is because DestroyBCE only // updates RCEs in the route cache. // RCE->BCE = NULL; } //* MoveToFrontRCE // // Move an RCE to the front of the list. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void MoveToFrontRCE(RouteCacheEntry *RCE) { if (RCE->Prev != SentinelRCE) { RouteCacheEntry *AfterRCE = SentinelRCE; // // Remove the RCE from its current location. // RCE->Prev->Next = RCE->Next; RCE->Next->Prev = RCE->Prev; // // And put it at the front. // RCE->Prev = AfterRCE; (RCE->Next = AfterRCE->Next)->Prev = RCE; AfterRCE->Next = RCE; } } //* GetCareOfRCE // // Get the CareOfRCE, if any, for the specified RCE. // // Note that a reference is obtained for the CareOfRCE // and donated to the caller. // // Callable from a thread or DPC context. // Called with NO locks held. // RouteCacheEntry * GetCareOfRCE(RouteCacheEntry *RCE) { KIRQL OldIrql; RouteCacheEntry *CareOfRCE = NULL; if (RCE->BCE != NULL) { KeAcquireSpinLock(&RouteCacheLock, &OldIrql); if (RCE->BCE != NULL) { CareOfRCE = RCE->BCE->CareOfRCE; AddRefRCE(CareOfRCE); } KeReleaseSpinLock(&RouteCacheLock, OldIrql); } return CareOfRCE; } //* IsLoopbackRCE // // Does the effective RCE correspond to a loopback path? // // Called with NO locks held. // int IsLoopbackRCE(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; int IsLoopback; CareOfRCE = GetCareOfRCE(RCE); if (CareOfRCE != NULL) RCE = CareOfRCE; // Update with the effective RCE. IsLoopback = RCE->NCE->IsLoopback; if (CareOfRCE != NULL) ReleaseRCE(CareOfRCE); return IsLoopback; } //* GetInitialRTTFromRCE // Helper routine to get interface specific RTT. // // Called with NO locks held. uint GetInitialRTTFromRCE(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; NeighborCacheEntry *NCE; uint RTT; CareOfRCE = GetCareOfRCE(RCE); NCE = (CareOfRCE ? CareOfRCE : RCE)->NCE; RTT = NCE->IF->TcpInitialRTT; if (CareOfRCE) ReleaseRCE(CareOfRCE); return RTT; } //* IsDisconnectedAndNotLoopbackRCE // // Does the effective RCE have a disconnected outgoing interface // and not correspond to a loopback path? // // Called with NO locks held. // int IsDisconnectedAndNotLoopbackRCE(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; int IsDisconnectedAndNotLoopback; CareOfRCE = GetCareOfRCE(RCE); if (CareOfRCE != NULL) RCE = CareOfRCE; // Update with the effective RCE. IsDisconnectedAndNotLoopback = !RCE->NCE->IsLoopback && (RCE->NCE->IF->Flags & IF_FLAG_MEDIA_DISCONNECTED); if (CareOfRCE != NULL) ReleaseRCE(CareOfRCE); return IsDisconnectedAndNotLoopback; } //* GetV4Destination // // If sending via the RCE will result in tunneling a packet // to an IPv4 destination, returns the IPv4 destination address. // Otherwise returns INADDR_ANY. // IPAddr GetV4Destination(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; NeighborCacheEntry *NCE; Interface *IF; IPAddr V4Dest; KIRQL OldIrql; CareOfRCE = GetCareOfRCE(RCE); if (CareOfRCE != NULL) RCE = CareOfRCE; // Update with the effective RCE. NCE = RCE->NCE; IF = NCE->IF; if (IsIPv4TunnelIF(IF)) { ASSERT(IF->LinkAddressLength == sizeof(IPAddr)); KeAcquireSpinLock(&IF->LockNC, &OldIrql); if (NCE->NDState != ND_STATE_INCOMPLETE) V4Dest = * (IPAddr UNALIGNED *) NCE->LinkAddress; else V4Dest = INADDR_ANY; KeReleaseSpinLock(&IF->LockNC, OldIrql); } else { V4Dest = INADDR_ANY; } if (CareOfRCE != NULL) ReleaseRCE(CareOfRCE); return V4Dest; } //* ValidateCareOfRCE // // Helper function for ValidateRCE and RouteToDestination. // // Checks that the CareOfRCE (RCE->BCE->CareOfRCE) is still valid, and // if not, releases the existing CareOfRCE and updates RCE->BCE with a // new RCE. // // If the attempt to get a new RCE fails, the BCE is destroyed. // // Called with the route cache locked. // void ValidateCareOfRCE(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; IPv6Addr *CareOfAddr; ushort CareOfScope; uint CareOfScopeId; RouteCacheEntry *NewRCE; IP_STATUS Status; ASSERT(RCE->BCE != NULL); CareOfRCE = RCE->BCE->CareOfRCE; if (CareOfRCE->Valid != RouteCacheValidationCounter) { // // Note that since we already hold the RouteCacheLock we // call FindOrCreateRoute instead of RouteToDestination. // Also, we assume the care-of address is scoped to the // same interface as before. // CareOfAddr = &(CareOfRCE->Destination); CareOfScope = AddressScope(CareOfAddr); CareOfScopeId = CareOfRCE->NTE->IF->ZoneIndices[CareOfScope]; Status = FindOrCreateRoute(CareOfAddr, CareOfScopeId, NULL, &NewRCE); if (Status == IP_SUCCESS) { // // Update the binding cache entry. // ReleaseRCE(CareOfRCE); RCE->BCE->CareOfRCE = NewRCE; } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "ValidateCareOfRCE(%p): FindOrCreateRoute failed: %x\n", CareOfRCE, Status)); // // Because we could not update the BCE, destroy it. // DestroyBCE(RCE->BCE); // // Destroy BCE should have removed our reference too. // ASSERT(RCE->BCE == NULL); } } } //* ValidateRCE // // Checks that an RCE is still valid, and if not, releases // the RCE and returns a reference for a new RCE. // In any case, returns a pointer to an RCE. // // REVIEW: Perhaps ValidateRCE should take an NTE argument. // On routers, if the caller is using a scoped source address, // which may be a different address than RCE->NTE, // then this will affect RouteToDestination. // // Called with NO locks held. // RouteCacheEntry * ValidateRCE(RouteCacheEntry *RCE) { if (RCE->Valid != RouteCacheValidationCounter) { RouteCacheEntry *NewRCE; IP_STATUS Status; // // Get a new RCE to replace the current RCE. // RouteToDestination will calculate ScopeId. // REVIEW: If this fails, then continue to use the current RCE. // This way our callers don't have to check for errors. // Status = RouteToDestination(&RCE->Destination, 0, CastFromNTE(RCE->NTE), RTD_FLAG_NORMAL, &NewRCE); if (Status == IP_SUCCESS) { ReleaseRCE(RCE); RCE = NewRCE; } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR, "ValidateRCE(%p): RouteToDestination failed: %x\n", RCE, Status)); } } // // Validate and update the CareOfRCE before we return. // if (RCE->BCE != NULL) { KIRQL OldIrql; KeAcquireSpinLock(&RouteCacheLock, &OldIrql); if (RCE->BCE != NULL) ValidateCareOfRCE(RCE); KeReleaseSpinLock(&RouteCacheLock, OldIrql); } return RCE; } //* CreateOrReuseRoute // // Creates a new RCE. Attempts to reuse an existing RCE. // // Called with the route cache lock held. // Callable from a thread or DPC context. // // Returns NULL if a new RCE can not be allocated. // The RefCnt field in the returned RCE is initialized to one. // // REVIEW: Currently we have an upper-bound on the number of RCEs. // Probably a better scheme would take into account the time // since last use. // RouteCacheEntry * CreateOrReuseRoute(void) { RouteCacheEntry *RCE; if (RouteCache.Count >= RouteCache.Limit) { // // First search backwards for an unused RCE. // for (RCE = RouteCache.Last; RCE != SentinelRCE; RCE = RCE->Prev) { if (RCE->RefCnt == 1) { // // We can reuse this RCE. // RemoveRCE(RCE); ReleaseNCE(RCE->NCE); ReleaseNTE(RCE->NTE); return RCE; } } } // // Create a new RCE. // RCE = ExAllocatePool(NonPagedPool, sizeof *RCE); if (RCE == NULL) return NULL; RCE->RefCnt = 1; return RCE; } //* RouteCacheCheck // // Check the route cache's consistency. Ensure that // a) There is only one RCE for an interface/destination pair, and // b) There is at most one valid unconstrained RCE for the destination. // // Called with the route cache locked. // #if DBG void RouteCacheCheck(RouteCacheEntry *CheckRCE, ulong CurrentValidationCounter) { const IPv6Addr *Dest = &CheckRCE->Destination; Interface *IF = CheckRCE->NTE->IF; ushort Scope = AddressScope(Dest); uint ScopeId = IF->ZoneIndices[Scope]; uint NumTotal = 0; uint NumUnconstrainedIF = 0; uint NumUnconstrained = 0; RouteCacheEntry *RCE; // // Scan the route cache looking for problems. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = RCE->Next) { NumTotal++; if (IP6_ADDR_EQUAL(&RCE->Destination, Dest)) { if (RCE->NTE->IF == IF) { // // There should only be one RCE in the cache // for an interface/destination pair. // (There may be other, invalid RCEs not in the cache.) // ASSERT(RCE == CheckRCE); } // // RCE_FLAG_CONSTRAINED_IF implies RCE_FLAG_CONSTRAINED_SCOPEID. // ASSERT(!(RCE->Flags & RCE_FLAG_CONSTRAINED_IF) || (RCE->Flags & RCE_FLAG_CONSTRAINED_SCOPEID)); if (RCE->Valid == CurrentValidationCounter) { if ((RCE->NTE->IF->ZoneIndices[Scope] == ScopeId) && !(RCE->Flags & RCE_FLAG_CONSTRAINED_IF)) NumUnconstrainedIF++; if (!(RCE->Flags & RCE_FLAG_CONSTRAINED)) NumUnconstrained++; } } } // // There should be at most one valid unconstrained RCE // for this scope-id/destination. // ASSERT(NumUnconstrainedIF <= 1); // // There should be at most one valid unconstrained RCE // for this destination. // ASSERT(NumUnconstrained <= 1); // // The total should be correct. // ASSERT(NumTotal == RouteCache.Count); } #else // DBG __inline void RouteCacheCheck(RouteCacheEntry *CheckRCE, ulong CurrentValidationCounter) { } #endif // DBG //* CanonicalizeScopeId // // Given an address and ScopeId, converts the ScopeId for internal usage. // Also returns the address scope. // // Returns FALSE if the ScopeId is invalid. // __inline int // Encourage the compiler to inline if it wishes. CanonicalizeScopeId( const IPv6Addr *Addr, uint *ScopeId, ushort *Scope) { // // The loopback address and global-scope addresses are special: // callers can supply a zero ScopeId without ambiguity. // See also DetermineScopeId and RouteToDestination. // For the moment, we enforce a zero ScopeId for those addresses // lest we confuse TCP & UDP by having two legal ScopeId values // for a single address which should be considered the same and // for which DetermineScopeId returns zero. // *Scope = AddressScope(Addr); if (IsLoopback(Addr)) { if (*ScopeId == 0) *ScopeId = LoopInterface->ZoneIndices[*Scope]; else return FALSE; } else if (*Scope == ADE_GLOBAL) { if (*ScopeId == 0) *ScopeId = 1; else return FALSE; } return TRUE; } //* RouteToDestination - Find a route to a particular destination. // // Finds an existing, or creates a new, route cache entry for // a particular destination. Note the destination address may // only be valid in a particular scope. // // The optional NTEorIF argument specifies the interface // and/or the source address that should be used to reach the destination. // The Flags argument affects the interpretation of NTEorIF. // If RTD_FLAG_STRICT, then NTEorIF constrains whether or not it specifies // a forwarding interface. If RTD_FLAG_LOOSE, then NTEorIF is only used // for determining/checking ScopeId and otherwise does not constrain. // // Called with NO locks held. // // Return codes: // IP_NO_RESOURCES Couldn't allocate memory. // IP_PARAMETER_PROBLEM Illegal Dest/ScopeId. // IP_BAD_ROUTE Bad NTEorIF for this destination, // or could not find an NTE. // IP_DEST_NO_ROUTE No way to reach the destination. // // IP_DEST_NO_ROUTE can only be returned if NTEorIF is NULL. // // NB: The return code values and situations in which they are used // in RouteToDestination and its helper functions must be carefully // considered, both for RouteToDestination's own correctness // and for the correctness of callers. // IP_STATUS // Returns: whether call was sucessful and/or why not. RouteToDestination( const IPv6Addr *Dest, // Destination address to route to. uint ScopeId, // Scope id for Dest (may be 0). NetTableEntryOrInterface *NTEorIF, // IF to send from (may be NULL). uint Flags, // Control optional behaviors. RouteCacheEntry **ReturnRCE) // Returns pointer to cached route. { Interface *IF; KIRQL OldIrql; IP_STATUS ReturnValue; ushort Scope; // // Pre-calculate Scope for scoped addresses (saves time in the loop). // Note that callers can supply ScopeId == 0 for a scoped address, // meaning that they are not constraining the scoped address // to a particular zone. // if (! CanonicalizeScopeId(Dest, &ScopeId, &Scope)) return IP_PARAMETER_PROBLEM; if (NTEorIF != NULL) { // // Our caller is constraining the originating interface. // IF = NTEorIF->IF; // // First, check this against ScopeId. // if (ScopeId == 0) ScopeId = IF->ZoneIndices[Scope]; else if (ScopeId != IF->ZoneIndices[Scope]) return IP_BAD_ROUTE; // // Depending on Flags and whether this is forwarding interface, // we may ignore this specification and look at all interfaces. // Logically, the packet is originated by the specified interface // but then internally forwarded to the outgoing interface. // (Although we will not decrement the Hop Count.) // As when forwarding, we check after finding the best route // if the route will cause the packet to leave // the scope of the source address. // // It is critical that the route cache lookup and FindNextHop // computation use only IF and not NTEorIF. This is necessary // for the maintenance of the cache invariants. Once we have // an RCE (or an error), then we can check against NTEorIF. // switch (Flags) { case RTD_FLAG_LOOSE: IF = NULL; break; case RTD_FLAG_NORMAL: if (IF->Flags & IF_FLAG_FORWARDS) IF = NULL; break; case RTD_FLAG_STRICT: break; default: ASSERT(! "bad RouteToDestination Flags"); break; } } else { // // Our caller is not constraining the originating interface. // IF = NULL; } KeAcquireSpinLock(&RouteCacheLock, &OldIrql); ReturnValue = FindOrCreateRoute(Dest, ScopeId, IF, ReturnRCE); if ((NTEorIF != NULL) && (IF == NULL) && (Flags == RTD_FLAG_NORMAL)) { // // Our caller specified a forwarding interface, // and we ignored the interface constraint. // There are a couple cases in which we should // retry, preserving the interface constraint. // NB: In the IPv6Forward paths, NTEorIF is NULL. // So this check only applies to originating packets. // if (ReturnValue == IP_SUCCESS) { if (IsNTE(NTEorIF)) { RouteCacheEntry *RCE = *ReturnRCE; NetTableEntry *NTE = CastToNTE(NTEorIF); Interface *OriginatingIF = NTE->IF; Interface *OutgoingIF = RCE->NTE->IF; // // Does this route carry the packet outside // the scope of the specified source address? // if (OutgoingIF->ZoneIndices[NTE->Scope] != OriginatingIF->ZoneIndices[NTE->Scope]) { ReleaseRCE(RCE); goto Retry; } } } else if (ReturnValue == IP_DEST_NO_ROUTE) { // // Retry, allowing the destination // to be considered on-link to the specified interface. // Retry: IF = NTEorIF->IF; ReturnValue = FindOrCreateRoute(Dest, ScopeId, IF, ReturnRCE); } } // // Validate and update the CareOfRCE before we return. // if ((ReturnValue == IP_SUCCESS) && ((*ReturnRCE)->BCE != NULL)) ValidateCareOfRCE(*ReturnRCE); KeReleaseSpinLock(&RouteCacheLock, OldIrql); return ReturnValue; } //* FindOrCreateRoute // // Helper function for RouteToDestination and RedirectRouteCache. // // See the RouteToDestination description of return codes. // IP_DEST_NO_ROUTE can only be returned if IF is NULL. // RouteToDestination may retry FindOrCreateRoute with a non-null IF // when it gets IP_DEST_NO_ROUTE. // // Called with the route cache locked. // IP_STATUS FindOrCreateRoute( const IPv6Addr *Dest, // Destination address to route to. uint ScopeId, // Scope id for Dest (0 if non-scoped). Interface *IF, // IF to send from (may be NULL). RouteCacheEntry **ReturnRCE) // Returns pointer to cached route. { ulong CurrentValidationCounter; RouteCacheEntry *SaveRCE = NULL; RouteCacheEntry *RCE; RouteCacheEntry *NextRCE; Interface *TmpIF; NeighborCacheEntry *NCE; NetTableEntry *NTE; ushort Constrained; KIRQL OldIrql; IP_STATUS ReturnValue; ushort Scope; // // Precompute and save some time in the loop. // Scope = AddressScope(Dest); ASSERT((IF == NULL) || ((ScopeId != 0) && (ScopeId == IF->ZoneIndices[Scope]))); // // For consistency, snapshot RouteCacheValidationCounter. // CurrentValidationCounter = RouteCacheValidationCounter; // // Check for an existing route cache entry. // There are two main cases. // // If IF is not NULL, then there is at most one matching RCE // in the cache. If this RCE does not validate, then we can use // the results of FindNextHop/FindBestSourceAddress when creating // the new RCE. // // If IF is NULL, then there may be more than one matching RCE. // We can only reuse the results of the validating FindNextHop/ // FindBestSourceAddress iff FindRoute returned Constrained == 0. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = NextRCE) { NextRCE = RCE->Next; // // We want a route to the requested destination, obviously. // if (!IP6_ADDR_EQUAL(Dest, &RCE->Destination)) continue; TmpIF = RCE->NTE->IF; // // Check for a caller-imposed interface constraint. // if (IF == NULL) { // // We're not constrained to a particular interface, so // there may be multiple routes to this destination in // the cache to choose from. Don't pick a constrained RCE. // if (RCE->Flags & RCE_FLAG_CONSTRAINED_IF) { // // If this RCE is invalid, then RCE_FLAG_CONSTRAINED_IF // might be stale information. We do not want to pass // by this RCE and then later create another RCE // for the same interface/destination pair. // if (RCE->Valid != CurrentValidationCounter) goto AttemptValidation; continue; } // // Check for a ScopeId constraint. // if (ScopeId == 0) { // // We're not constrained to a particular zone, so // there may be multiple routes to this destination in // the cache to choose from. Don't pick a constrained RCE. // if (RCE->Flags & RCE_FLAG_CONSTRAINED_SCOPEID) { // // If this RCE is invalid, RCE_FLAG_CONSTRAINED_SCOPEID // might be stale information. We do not want to pass // by this RCE and then later create another RCE // for the same interface/destination pair. // if (RCE->Valid != CurrentValidationCounter) goto AttemptValidation; continue; } } else { // // We're constrained to a particular zone. // If this route uses a different one, keep looking. // if (ScopeId != TmpIF->ZoneIndices[Scope]) continue; } } else { // // We're constrained to a particular interface. // If this route uses a different one, keep looking. // if (IF != TmpIF) continue; ASSERT((ScopeId != 0) && (ScopeId == TmpIF->ZoneIndices[Scope])); } // // At this point, we have a RCE that matches our criteria. // As long as the RCE is still valid, we're done. // if (RCE->Valid == CurrentValidationCounter) { IF = TmpIF; goto ReturnRCE; } AttemptValidation: // // Something has changed in the routing state since the last // time this RCE was validated. Attempt to revalidate it. // We calculate a new NTE and NCE for this destination, // restricting ourselves to sending from the same interface. // Note that because we are validating an RCE, // the arguments to FindNextHop are completely dependent on // the contents of the RCE, not the arguments to FindOrCreateRoute. // ReturnValue = FindNextHop(TmpIF, Dest, TmpIF->ZoneIndices[Scope], &NCE, &Constrained); if (ReturnValue != IP_SUCCESS) goto RemoveAndContinue; ASSERT((IF == NULL) || (IF == TmpIF)); ASSERT(TmpIF == RCE->NTE->IF); ASSERT(TmpIF == RCE->NCE->IF); NTE = FindBestSourceAddress(TmpIF, Dest); if (NTE == NULL) { ReleaseNCE(NCE); RemoveAndContinue: // // Bad news for this RCE. // We must remove it from the cache // before we continue searching, // lest we inadvertently create a second RCE // for the same interface/destination pair. // RemoveRCE(RCE); ReleaseRCE(RCE); continue; } // // If our new calculations yield the same NTE and NCE that // are present in the existing RCE, than we can just validate it. // Note that we check the NCE even if this is a Redirect RCE. // If the routing table changes, we want to start over with // a new first-hop, which might just redirect us again. Or might not. // if ((RCE->NTE == NTE) && (RCE->NCE == NCE) && (RCE->Flags == Constrained)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindOrCreateRoute - validating RCE %p\n", RCE)); RCE->Valid = CurrentValidationCounter; ReleaseNCE(NCE); ReleaseNTE(NTE); // // We need to check again that the RCE meets the criteria. // We may have checked the RCE validity because the RCE // appeared to be constrained and we need an unconstrained RCE. // So in that case, if the RCE validated we can't actually use it. // NB: ScopeId == 0 implies IF == NULL. // if ((ScopeId == 0) ? (Constrained & RCE_FLAG_CONSTRAINED) : ((IF == NULL) && (Constrained & RCE_FLAG_CONSTRAINED_IF))) continue; IF = TmpIF; goto ReturnRCE; } // // We can't just validate the existing RCE, we need to update // it. If the RCE has exactly one reference, we could update it // in place (this wouldn't work if it has more than one reference // since there is no way to signal the RCE's other users that the // NCE and/or NTE it caches has changed). But this wouldn't help // the case where we are called from ValidateRCE. And it would // require some care as to which information in the RCE is still // valid. So we ignore this optimization opportunity and will // create a new RCE instead. // // However, we can take advantage of another optimization. As // long as we're still limiting our interface choice to the one // that is present in the existing (invalid) RCE, and there isn't // a better route available, then we can use the NCE and NTE we // got from FindNextHop and FindBestSourceAddress above to create // our new RCE since we aren't going to find a better one. // NB: ScopeId == 0 implies IF == NULL. // if ((ScopeId == 0) ? !(Constrained & RCE_FLAG_CONSTRAINED) : ((IF != NULL) || !(Constrained & RCE_FLAG_CONSTRAINED_IF))) { // // Since some of the state information in the existing RCE // is still valid, we hang onto it so we can use it later // when creating the new RCE. We assume ownership of the // cache's reference for the invalid RCE. // KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindOrCreateRoute - saving RCE %p\n", RCE)); RemoveRCE(RCE); SaveRCE = RCE; IF = TmpIF; goto HaveNCEandNTE; } ReleaseNTE(NTE); ReleaseNCE(NCE); // // Not valid, we keep looking for a valid matching RCE. // KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindOrCreateRoute - invalid RCE %p\n", RCE)); } // // No existing RCE found. Before creating a new RCE, // we determine a next-hop neighbor (NCE) and // a best source address (NTE) for this destination. // The order is important: we want to avoid churning // the cache via CreateOrReuseRoute if we will just // get an error anyway. // This prevents a denial-of-service attack. // ReturnValue = FindNextHop(IF, Dest, ScopeId, &NCE, &Constrained); if (ReturnValue != IP_SUCCESS) goto ReturnError; ASSERT((IF == NULL) || (IF == NCE->IF)); IF = NCE->IF; // // Find the best source address for this destination. // (The NTE from our caller might not be the best.) // By restricting ourselves to the interface returned // by FindNextHop above, we know we haven't left our // particular scope. // NTE = FindBestSourceAddress(IF, Dest); if (NTE == NULL) { // // We have no valid source address to use! // ReturnValue = IP_BAD_ROUTE; ReleaseNCE(NCE); goto ReturnError; } HaveNCEandNTE: // // Get a new route cache entry. // Because SaveRCE was just removed from the cache, // CreateOrReuseRoute will not find it. // RCE = CreateOrReuseRoute(); if (RCE == NULL) { ReturnValue = IP_NO_RESOURCES; ReleaseNTE(NTE); ReleaseNCE(NCE); goto ReturnError; } // // FindOrCreateNeighbor/FindRoute (called from FindNextHop) gave // us a reference for the NCE. We donate that reference to the RCE. // Similarly, FindBestSourceAddress gave us a reference // for the NTE and we donate the reference to the RCE. // RCE->NCE = NCE; RCE->NTE = NTE; RCE->PathMTU = IF->LinkMTU; RCE->PMTULastSet = 0; // PMTU timer not running. RCE->Destination = *Dest; RCE->Type = RCE_TYPE_COMPUTED; RCE->Flags = Constrained; // Start with a value safely in the past. RCE->LastError = IPv6TickCount - ICMP_MIN_ERROR_INTERVAL; RCE->BCE = FindBindingCacheEntry(Dest); RCE->Valid = CurrentValidationCounter; // // Copy state from a previous RCE for this destination, // if we have it and the state is relevant. // if (SaveRCE != NULL) { ASSERT(SaveRCE->NTE->IF == RCE->NTE->IF); // // PathMTU is relevant if the next-hop neighbor is unchanged. // if (RCE->NCE == SaveRCE->NCE) { RCE->PathMTU = SaveRCE->PathMTU; RCE->PMTULastSet = SaveRCE->PMTULastSet; } // // ICMP rate-limiting information is always relevant. // RCE->LastError = SaveRCE->LastError; } // // Add the new route cache entry to the cache. // InsertRCE(RCE); ReturnRCE: // // If the RCE is not at the front of the cache, move it there. // MoveToFrontRCE(RCE); ASSERT(IF == RCE->NTE->IF); // // Check route cache consistency. // RouteCacheCheck(RCE, CurrentValidationCounter); AddRefRCE(RCE); ASSERT(RCE->RefCnt >= 2); // One held by the cache, one for our caller. *ReturnRCE = RCE; ReturnValue = IP_SUCCESS; ReturnError: if (SaveRCE != NULL) ReleaseRCE(SaveRCE); return ReturnValue; } //* FindNextHop // // Calculate the next hop to use for the destination. // // FindNextHop is just FindRoute plus some extra logic // to handle the case where there is no route: // RFC 2461 Section 5.2 specifies "If the Default Router List // is empty, the sender assumes that the destination is on-link." // The question is, on-link to which interface? // // Callable from DPC context, not from thread context. // May be called with the RouteCacheLock held. // IP_STATUS FindNextHop( Interface *IF, // Outgoing IF (may be NULL). const IPv6Addr *Dest, // Destination address to route to. uint ScopeId, // Scope id for Dest (0 if non-scoped). NeighborCacheEntry **ReturnNCE, // NCE for next hop. ushort *ReturnConstrained) { NeighborCacheEntry *NCE; IP_STATUS Status; ushort Constrained; ushort Scope; Interface *ScopeIF; Scope = AddressScope(Dest); ASSERT((IF == NULL) || ((ScopeId != 0) && (ScopeId == IF->ZoneIndices[Scope]))); // // An unspecified destination is never legal here. // if (IsUnspecified(Dest)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR, "FindNextHop - inappropriate dest?\n")); return IP_PARAMETER_PROBLEM; } // // FindRoute treats link-local and multicast destinations // somewhat specially. It also checks for on-link prefixes. // It implements the On-Link Prefix List and Default Router List // lookups specified in RFC 2461 section 5.2. // Status = FindRoute(IF, Dest, ScopeId, &NCE, ReturnConstrained); if (Status == IP_SUCCESS) goto ReturnSuccess; // // We have no route for the destination. // We should treat the destination as being on-link. // But on-link to which interface? // Status = FindDefaultInterfaceForZone(Scope, ScopeId, &ScopeIF, &Constrained); // FindDefaultInterfaceForZone must always initialize ScopeIF. if (IF != NULL) { // // Assume that the destination is on-link // to the specified interface. // AddRefIF(IF); if (IF == ScopeIF) { // // If the IF argument were not specified, // we would have chosen this same interface. // Use Constrained value from FindDefaultInterfaceForZone. // } else { // // We needed the IF argument. // Constrained = RCE_FLAG_CONSTRAINED; } if (ScopeIF != NULL) ReleaseIF(ScopeIF); } else if (ScopeIF == NULL) { // // FindUniqueInterfaceFromZone returned either IP_DEST_NO_ROUTE // (meaning the ScopeId did not specify a default interface) // or IP_PARAMETER_PROBLEM (meaning the ScopeId was invalid). // ASSERT((Status == IP_DEST_NO_ROUTE) || (Status == IP_PARAMETER_PROBLEM)); return Status; } else { // // Use the default interface in the correct zone. // Use Constrained value from FindDefaultInterfaceForZone. // IF = ScopeIF; } // // Assume that the destination address is on-link. // Search the interface's neighbor cache for the destination. // NCE = FindOrCreateNeighbor(IF, Dest); ReleaseIF(IF); if (NCE == NULL) { return IP_NO_RESOURCES; } *ReturnConstrained = Constrained; ReturnSuccess: *ReturnNCE = NCE; return IP_SUCCESS; } //* CompareRoutes // // Compares the desirability of two routes. // >0 means A is preferred, // 0 means no preference, // <0 means B is preferred. // // It is very important that the comparison relation be transitive, // to achieve predictable route selection. // // Called with the route table locked. // int CompareRoutes( RouteTableEntry *A, int Areachable, RouteTableEntry *B, int Breachable) { uint Apref, Bpref; // // Compare reachability. // if (Areachable > Breachable) return 1; // Prefer A. else if (Breachable > Areachable) return -1; // Prefer B. // // Compare prefix length. // if (A->PrefixLength > B->PrefixLength) return 1; // Prefer A. else if (B->PrefixLength > A->PrefixLength) return -1; // Prefer B. // // Compare preference. // Route & interface preference values are restricted // so that these additions do not overflow. // Apref = A->IF->Preference + A->Preference; Bpref = B->IF->Preference + B->Preference; if (Apref < Bpref) return 1; // Prefer A. else if (Bpref < Apref) return -1; // Prefer B. return 0; // No preference. } //* FindRoute // // Given a destination address, checks the list of routes // using the longest-matching-prefix algorithm // to decide if we have a route to this address. // If so, returns the neighbor through which we should route. // // If the optional IF is supplied, then this constrains the lookup // to only use routes via the specified outgoing interface. // If IF is specified then ScopeId should be specified. // // If the optional ScopeId is supplied, then this constraints the lookup // to only use routes via interfaces in the correct zone for the // scope of the destination address. // // The ReturnConstrained parameter returns an indication of whether the // IF and ScopeId parameters constrained the returned NCE. // That is, if IF is NULL and ScopeId is non-zero (for scoped destinations) // then Constrained is always returned as zero. If IF is non-NULL and // a different NCE is returned than would have been returned if IF were // NULL, then Constrained is returned with RCE_FLAG_CONSTRAINED_IF set. // Similarly, if ScopeId is non-zero and a different NCE is returned // than would have been returned if ScopeId were zero, then Constrained // is returned with RCE_FLAG_CONSTRAINED_SCOPEID set. // // NOTE: Any code path that changes any state used by FindRoute // must use InvalidateRouteCache. // // Callable from DPC context, not from thread context. // May be called with the RouteCacheLock held. // IP_STATUS FindRoute( Interface *IF, const IPv6Addr *Dest, uint ScopeId, NeighborCacheEntry **ReturnNCE, ushort *ReturnConstrained) { RouteTableEntry *RTE, **PrevRTE; NeighborCacheEntry *NCE; uint MinPrefixLength; NeighborReachability Reachable; ushort Scope; // // These variables track the best route that we can actually return, // subject to the IF and ScopeId constraints. // NeighborCacheEntry *BestNCE = NULL; // Holds a reference. RouteTableEntry *BestRTE; // Initialized when BestNCE is non-NULL. NeighborReachability BestReachable; // Used when BestNCE is non-NULL. // // These variables track the best route in the right zone. // They are only used if IF != NULL. // NeighborCacheEntry *BzoneNCE = NULL; // Does NOT hold a reference. RouteTableEntry *BzoneRTE; // Initialized when BzoneNCE is non-NULL. NeighborReachability BzoneReachable; // Used when BzoneNCE is non-NULL. // // These variables track the best unconstrained route. // They are only used if IF != NULL or ScopeId != 0: // in other words, if there is some constraint. // NeighborCacheEntry *BallNCE = NULL; // Does NOT hold a reference. RouteTableEntry *BallRTE; // Initialized when BallNCE is non-NULL. NeighborReachability BallReachable; // Used when BallNCE is non-NULL. // // Keep track of whether there could be a better route // than the one we are returning, if a neighbor that is // currently unreachable were reachable instead. // int CouldBeBetterReachable = FALSE; // // Keep track of whether the destination could be on-link // to an interface. // int CouldBeBetterOnLink = FALSE; // // We enforce a minimum prefix length for "on-link" addresses. // If we match a route that is shorter than the minimum prefix length, // we treat the route as if it were on-link. The net effect is // that a default route implies a default interface for multicast // and link-local destinations. This may of course be overridden // with the appropriate more-specific /8 or /10 route. // if (IsMulticast(Dest)) MinPrefixLength = 8; else if (IsLinkLocal(Dest)) MinPrefixLength = 10; else MinPrefixLength = 0; // // Calculate the scope of the destination address. // Scope = AddressScope(Dest); ASSERT((IF == NULL) || ((ScopeId != 0) && (ScopeId == IF->ZoneIndices[Scope]))); KeAcquireSpinLockAtDpcLevel(&RouteTableLock); PrevRTE = &RouteTable.First; while ((RTE = *PrevRTE) != NULL) { // // Does this route's prefix match that of our destination address? // if ((RTE->ValidLifetime != 0) && (RTE->PrefixLength >= MinPrefixLength) && HasPrefix(Dest, &RTE->Prefix, RTE->PrefixLength)) { // // We have a match against a potential route. // Get a pointer to the next hop. // if (IsOnLinkRTE(RTE)) { // // Note that in some situations we will create an NCE // that we will end up not using. That's OK. // NCE = FindOrCreateNeighbor(RTE->IF, Dest); if (NCE == NULL) { // // Couldn't create a new neighbor. // Just bail out now. // KeReleaseSpinLockFromDpcLevel(&RouteTableLock); if (BestNCE != NULL) ReleaseNCE(BestNCE); return IP_NO_RESOURCES; } } else { NCE = RTE->NCE; AddRefNCE(NCE); } // // Note that reachability state transitions // must invalidate the route cache. // A negative return value indicates // that the neighbor was just found to be unreachable // so we should round-robin. // Reachable = GetReachability(NCE); if (Reachable < 0) { // // Time for round-robin. Move this route to the end // and continue. The next time we get to this route, // GetReachability will return a non-negative value. // // Because round-robin perturbs route table state, // it "should" invalidate the route cache. However, // this isn't necessary. The route cache is invalidated // when NCE->DoRoundRobin is set to TRUE, and the // round-robin is actually performed by FindRoute before // returning any result that could depend on this // route's position in the route table. // ReleaseNCE(NCE); RemoveRTE(PrevRTE, RTE); InsertRTEAtBack(RTE); continue; } // // Track the best route that we can actually return, // subject to the IF and ScopeId constraints. // if ((IF == NULL) ? ((ScopeId == 0) || (ScopeId == RTE->IF->ZoneIndices[Scope])) : (IF == RTE->IF)) { if (IsOnLinkRTE(RTE)) CouldBeBetterOnLink = TRUE; if (BestNCE == NULL) { // // This is the first suitable next hop, // so remember it. // RememberBest: AddRefNCE(NCE); BestNCE = NCE; BestRTE = RTE; BestReachable = Reachable; } else { int Better; Better = CompareRoutes(RTE, Reachable, BestRTE, BestReachable); // // If this is a route via a currently-unreachable neighbor, // check if it appears that it might be a better route // if the neighbor were reachable. // if (!CouldBeBetterReachable && (Reachable == NeighborUnreachable) && (CompareRoutes(RTE, NeighborMayBeReachable, BestRTE, BestReachable) > Better)) CouldBeBetterReachable = TRUE; if (Better > 0) { // // This next hop looks better. // ReleaseNCE(BestNCE); goto RememberBest; } } } // // If there is a constraining interface, we also track // the best next hop subject to the ScopeId constraint // but NOT the interface constraint. Note that we do NOT // hold a reference for BzoneNCE. All we need to know // eventually is whether BzoneNCE == BestNCE and // we hold a ref for BestNCE, which is good enough. // if ((IF != NULL) && (ScopeId == RTE->IF->ZoneIndices[Scope])) { if (BzoneNCE == NULL) { // // This is the first suitable next hop, // so remember it. // RememberBzone: BzoneNCE = NCE; BzoneRTE = RTE; BzoneReachable = Reachable; } else if (CompareRoutes(RTE, Reachable, BzoneRTE, BzoneReachable) > 0) { // // This next hop looks better. // goto RememberBzone; } } // // If there is a constraining interface or ScopeId, we also // track the best overall next hop. Note that we do NOT // hold a reference for BallNCE. All we need to know // eventually is whether BallNCE == BestNCE and // we hold a ref for BestNCE, which is good enough. // if ((IF != NULL) || (ScopeId != 0)) { if (BallNCE == NULL) { // // This is the first suitable next hop, // so remember it. // RememberBall: BallNCE = NCE; BallRTE = RTE; BallReachable = Reachable; } else if (CompareRoutes(RTE, Reachable, BallRTE, BallReachable) > 0) { // // This next hop looks better. // goto RememberBall; } } ReleaseNCE(NCE); } // // Move on to the next route. // PrevRTE = &RTE->Next; } ASSERT(PrevRTE == RouteTable.Last); // // If the destination could be on-link and we actually selected // an on-link route, then we are OK. Otherwise, we need to check // if the destination could be on-link to the interface // that we selected. This implements one aspect of RFC 2461's // conceptual sending algorithm - the Prefix List is consulted // before the Default Router List. Note that RFC 2461 does not // consider multi-interface hosts and we only enforce a preference for // on-link routes within the context of a single interface. // If we choose a router on an interface when we could have chosen // on-link to the interface, the router would presumably just // Redirect us, so it's better to just send on-link even if the // destination is not reachable on-link. If the destination // is on-link but not reachable via one interface, // then we are happy to send off-link via another interface. // This may or may not succeed in reaching the destination, // but at least it has a chance of succeeding. // The CouldBeBetterReachable code will periodically probe // the destination's on-link reachability. // if (CouldBeBetterOnLink && IsOnLinkRTE(BestRTE)) CouldBeBetterOnLink = FALSE; if (CouldBeBetterReachable || CouldBeBetterOnLink) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindRoute: 2nd pass: Dest %s " "CBBReachable %d CBBOnLink %d BestRTE %p BestNCE %p\n", FormatV6Address(Dest), CouldBeBetterReachable, CouldBeBetterOnLink, BestRTE, BestNCE)); // // Make a second pass over the routes. // for (RTE = RouteTable.First; RTE != NULL; RTE = RTE->Next) { // // Does this route's prefix match that of our destination address? // And check our interface/scope-id constraints. // if ((RTE->ValidLifetime != 0) && (RTE->PrefixLength >= MinPrefixLength) && HasPrefix(Dest, &RTE->Prefix, RTE->PrefixLength) && ((IF == NULL) ? ((ScopeId == 0) || (ScopeId == RTE->IF->ZoneIndices[Scope])) : (IF == RTE->IF))) { // // Would this be a better route // if the neighbor were reachable? // if (CouldBeBetterReachable && CompareRoutes(RTE, NeighborMayBeReachable, BestRTE, BestReachable) > 0) { // // OK, we want to know if this neighbor becomes reachable, // because if it does we should change our routing. // if (IsOnLinkRTE(RTE)) NCE = FindOrCreateNeighbor(RTE->IF, Dest); else AddRefNCE(NCE = RTE->NCE); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindRoute: CBBReachable: " "BestRTE %p BestNCE %p RTE %p NCE %p\n", BestRTE, BestNCE, RTE, NCE)); if (NCE != NULL) { NeighborCacheProbeUnreachability(NCE); ReleaseNCE(NCE); } } // // Is this an on-link route on the same interface // that we chosen to use off-link? // if (CouldBeBetterOnLink && IsOnLinkRTE(RTE) && (RTE->IF == BestRTE->IF)) { // // OK, we want to send directly to this destination. // Switch to the on-link NCE. // NCE = FindOrCreateNeighbor(RTE->IF, Dest); ReleaseNCE(BestNCE); if (NCE == NULL) { KeReleaseSpinLockFromDpcLevel(&RouteTableLock); return IP_NO_RESOURCES; } KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "FindRoute: CBBOnLink: " "BestRTE %p BestNCE %p RTE %p NCE %p\n", BestRTE, BestNCE, RTE, NCE)); if (BallNCE == BestNCE) BallNCE = NCE; if (BzoneNCE == BestNCE) BzoneNCE = NCE; BestNCE = NCE; CouldBeBetterOnLink = FALSE; } } } } // // We can drop the lock and still do comparisons // against BallNCE and BzoneNCE. // KeReleaseSpinLockFromDpcLevel(&RouteTableLock); if (BestNCE != NULL) { *ReturnNCE = BestNCE; if ((ScopeId == 0) || (BestNCE == BallNCE)) { // // The IF and ScopeId arguments did not affect our BestNCE choice. // *ReturnConstrained = 0; } else if ((IF == NULL) || (BestNCE == BzoneNCE)) { // // The IF argument did not affect our BestNCE choice, // but the ScopeId argument did. // *ReturnConstrained = RCE_FLAG_CONSTRAINED_SCOPEID; } else { // // The IF argument affected our BestNCE choice. // *ReturnConstrained = RCE_FLAG_CONSTRAINED; } return IP_SUCCESS; } else { // // Didn't find a suitable next hop. // return IP_DEST_NO_ROUTE; } } //* FlushRouteCache // // Flushes entries from the route cache. // The Interface or the Address can be left unspecified. // in which case all relevant entries are flushed. // // Note that even if an RCE has references, // we can still remove it from the route cache. // It will continue to exist until its ref count falls to zero, // but subsequent calls to RouteToDestination will not find it. // // Called with NO locks held. // Callable from thread or DPC context. // void FlushRouteCache(Interface *IF, const IPv6Addr *Addr) { RouteCacheEntry *Delete = NULL; RouteCacheEntry *RCE, *NextRCE; KIRQL OldIrql; // // REVIEW: If both IF and Addr are specified, // we can bail out of the loop early. // KeAcquireSpinLock(&RouteCacheLock, &OldIrql); for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = NextRCE) { NextRCE = RCE->Next; if (((IF == NULL) || (IF == RCE->NTE->IF)) && ((Addr == NULL) || IP6_ADDR_EQUAL(Addr, &RCE->Destination))) { // // We can remove this RCE from the cache. // RemoveRCE(RCE); // // Put it on our delete list. // RCE->Next = Delete; Delete = RCE; } } KeReleaseSpinLock(&RouteCacheLock, OldIrql); // // Release the RCE references that were held by the route cache. // while (Delete != NULL) { RCE = Delete; Delete = RCE->Next; // // Prevent use of this RCE by anyone who has it cached. // InvalidateRCE(RCE); ReleaseRCE(RCE); } } //* ReleaseRCE // // Releases a reference to an RCE. // Sometimes called with the route cache lock held. // void ReleaseRCE(RouteCacheEntry *RCE) { if (InterlockedDecrement(&RCE->RefCnt) == 0) { // // This RCE should be deallocated. // It has already been removed from the cache. // ReleaseNTE(RCE->NTE); ReleaseNCE(RCE->NCE); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Freeing RCE: %p\n",RCE)); ExFreePool(RCE); } } //* FindNetworkWithAddress - Locate NTE with corresponding address and scope. // // Convert a source address to an NTE by scanning the list of NTEs, // looking for an NTE with this address. If the address is scope // specific, the ScopeId provided should identify the scope. // // Returns NULL if no matching NTE is found. // If found, returns a reference for the NTE. // NetTableEntry * FindNetworkWithAddress(const IPv6Addr *Source, uint ScopeId) { ushort Scope; NetTableEntry *NTE; KIRQL OldIrql; // // Canonicalize ScopeId and get Scope. // if (! CanonicalizeScopeId(Source, &ScopeId, &Scope)) return NULL; KeAcquireSpinLock(&NetTableListLock, &OldIrql); // // Loop through all the NTEs on the NetTableList. // for (NTE = NetTableList; ; NTE = NTE->NextOnNTL) { if (NTE == NULL) goto Return; // // Have we found an NTE with matching address and ScopeId? // if (IP6_ADDR_EQUAL(&NTE->Address, Source) && (ScopeId == NTE->IF->ZoneIndices[Scope])) { // // Check that this NTE is valid. // (For example, an NTE still doing DAD is invalid.) // if (!IsValidNTE(NTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "FindNetworkWithAddress: invalid NTE\n")); NTE = NULL; goto Return; } break; } } AddRefNTE(NTE); Return: KeReleaseSpinLock(&NetTableListLock, OldIrql); return NTE; } //* InvalidateRouter // // Called when we know a neighbor is no longer a router. // This function implements RFC 2461 section 7.3.3 - // when a node detects that a router has changed to a host, // the node MUST remove it from the Default Router List. // For our implementation, this means removing autoconfigured // routes from the routing table. // // Callable from a thread or DPC context. // Called with NO locks held. // void InvalidateRouter(NeighborCacheEntry *NCE) { CheckRtChangeContext Context; RouteTableEntry *RTE, **PrevRTE; InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Invalidating routes with NCE=%p\n", NCE)); PrevRTE = &RouteTable.First; while((RTE = *PrevRTE) != NULL) { if ((RTE->NCE == NCE) && (RTE->Type == RTE_TYPE_AUTOCONF)) { // // Remove the RTE from the list. // KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "InvalidateRouter: removed RTE %p\n", RTE)); RemoveRTE(PrevRTE, RTE); // // Check for matching route change notification requests. // CheckRtChangeNotifyRequests(&Context, NULL, RTE); // // Release the RTE. // ReleaseNCE(NCE); ExFreePool(RTE); } else { PrevRTE = &RTE->Next; } } ASSERT(PrevRTE == RouteTable.Last); // // Invalidate the route cache, even if the routing table has not changed. // We must invalidate any RCEs that are using this NCE, // perhaps because of a Redirect. // InvalidateRouteCache(); KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } } //* RouteTableUpdate - Update the route table. // // Updates the route table by creating a new route // or modifying the lifetime of an existing route. // // If the NCE is null, then the prefix is on-link. // Otherwise the NCE specifies the next hop. // // REVIEW - Should we do anything special when we get identical // routes with different next hops? Currently they both end up // in the table, and FindRoute tries to pick the best one. // // Note that the ValidLifetime may be INFINITE_LIFETIME, // whereas Neighbor Discovery does not allow an infinite value // for router lifetimes on the wire. // // The Publish and Immortal boolean arguments control // the respective flag bits in the RTE. // // The FileObject identifies the requestor of this update. // It is used to suppress some route change notifications. // It should be NULL for updates originating in the stack. // // The Type argument specifies the origin of the route (RTE_TYPE_* values). // The stack itself doesn't care about most of the values. // The exceptions are RTE_TYPE_SYSTEM, RTE_TYPE_MANUAL, RTE_TYPE_AUTOCONF. // System routes (used for loopback) can not be created/deleted by users. // Manual routes are affected by RouteTableReset. // Auto-configured routes are affected by RouteTableResetAutoConfig. // The Type of a route can not be updated after it is created. // // System routes and published routes survive in the route table // even when their lifetime is zero. (Then they do not affect routing.) // To delete a system route, specify a lifetime and type of zero. // // Error return values: // STATUS_INSUFFICIENT_RESOURCES - Failed to allocate pool. // STATUS_ACCESS_DENIED - Caller can not create/delete system route. // STATUS_INVALID_PARAMETER_1 - Interface is disabled. // STATUS_INVALID_PARAMETER_6 - Can not create route with zero Type. // These values are chosen for the convenience of IoctlUpdateRouteTable, // because our other callers only care about success/failure. // // Callable from a thread or DPC context. // May be called with an interface lock held. // NTSTATUS RouteTableUpdate( PFILE_OBJECT FileObject, Interface *IF, NeighborCacheEntry *NCE, const IPv6Addr *RoutePrefix, uint PrefixLength, uint SitePrefixLength, uint ValidLifetime, uint PreferredLifetime, uint Pref, uint Type, int Publish, int Immortal) { CheckRtChangeContext Context; IPv6Addr Prefix; RouteTableEntry *RTE, **PrevRTE; int Delete; NTSTATUS Status = STATUS_SUCCESS; ASSERT((NCE == NULL) || (NCE->IF == IF)); ASSERT(SitePrefixLength <= PrefixLength); ASSERT(PreferredLifetime <= ValidLifetime); ASSERT(IsValidRouteTableType(Type)); // // Ensure that the unused prefix bits are zero. // This makes the prefix comparisons below safe. // CopyPrefix(&Prefix, RoutePrefix, PrefixLength); Delete = FALSE; InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); if (IsDisabledIF(IF)) { // // Do not create routes on disabled interfaces. // This check must be made after locking the route table, // to prevent races with DestroyIF/RouteTableRemove. // Status = STATUS_INVALID_PARAMETER_1; } else { // // Search for an existing Route Table Entry. // for (PrevRTE = &RouteTable.First; ; PrevRTE = &RTE->Next) { RTE = *PrevRTE; if (RTE == NULL) { ASSERT(PrevRTE == RouteTable.Last); // // No existing entry for this prefix. // Create an entry if the lifetime is non-zero // or this is a published route or a system route. // if ((ValidLifetime != 0) || Publish || (Type == RTE_TYPE_SYSTEM)) { if ((Type == RTE_TYPE_SYSTEM) && (FileObject != NULL)) { // // Users can not create system routes. // Status = STATUS_ACCESS_DENIED; break; } if (Type == 0) { // // The zero Type value can only be used // for updating and deleting routes. // Status = STATUS_INVALID_PARAMETER_6; break; } RTE = ExAllocatePool(NonPagedPool, sizeof *RTE); if (RTE == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RouteTableUpdate: out of pool\n")); Status = STATUS_INSUFFICIENT_RESOURCES; break; } RTE->Type = (ushort)Type; RTE->Flags = 0; RTE->IF = IF; if (NCE != NULL) AddRefNCE(NCE); RTE->NCE = NCE; RTE->Prefix = Prefix; RTE->PrefixLength = PrefixLength; RTE->SitePrefixLength = SitePrefixLength; RTE->ValidLifetime = ValidLifetime; RTE->PreferredLifetime = PreferredLifetime; RTE->Preference = Pref; if (Publish) { RTE->Flags |= RTE_FLAG_PUBLISH; ForceRouterAdvertisements = TRUE; } if (Immortal) RTE->Flags |= RTE_FLAG_IMMORTAL; // // Add the new entry to the route table. // InsertRTEAtFront(RTE); if (ValidLifetime != 0) { // // Invalidate the route cache, so the new route // actually gets used. // InvalidateRouteCache(); } else { // // Don't notify about this route, // since it is being created as invalid. // RTE = NULL; } } break; } if ((RTE->IF == IF) && (RTE->NCE == NCE) && IP6_ADDR_EQUAL(&RTE->Prefix, &Prefix) && (RTE->PrefixLength == PrefixLength)) { // // We have an existing route. // Remove the route if the new lifetime is zero // (and the route is not published or a system route) // otherwise update the route. // The Type == 0 clause allows system routes to be deleted. // if ((ValidLifetime == 0) && !Publish && ((RTE->Type != RTE_TYPE_SYSTEM) || (Type == 0))) { if ((RTE->Type == RTE_TYPE_SYSTEM) && (FileObject != NULL)) { // // Users can not delete system routes. // Status = STATUS_ACCESS_DENIED; break; } // // Remove the RTE from the list. // See similar code in RouteTableTimeout. // RemoveRTE(PrevRTE, RTE); if (IsOnLinkRTE(RTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> IF %p removed\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->IF)); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> NCE %p removed\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->NCE)); // // Although we release the RTE's reference for the NTE, // our caller still holds a reference so RTE->NCE // is still valid for CheckRtChangeNotifyRequests. // ReleaseNCE(RTE->NCE); } // // If we are removing a published route, // resend Router Advertisements promptly. // if (RTE->Flags & RTE_FLAG_PUBLISH) ForceRouterAdvertisements = TRUE; if (RTE->ValidLifetime == 0) { // // This route was already invalid. // Delete the route structure now. // ExFreePool(RTE); // // Don't notify a route change; // that was done when the route became invalid. // RTE = NULL; } else { // // Invalidate all cached routes, // since we are removing a valid route. // InvalidateRouteCache(); // // Delete the route structure after checking // for route change notifications below. // Delete = TRUE; // // Update the route lifetimes, so the route info // returned in the notification shows zero lifetime. // But preserve the other route attributes. // RTE->ValidLifetime = RTE->PreferredLifetime = 0; } } else { uint OldLifetime = RTE->PreferredLifetime; // // If we are changing a published attribute of a route, // or if we are changing the publishing status of a route, // then resend Router Advertisements promptly. // if ((Publish && ((RTE->ValidLifetime != ValidLifetime) || (RTE->PreferredLifetime != PreferredLifetime) || (RTE->SitePrefixLength != SitePrefixLength))) || (!Publish != !(RTE->Flags & RTE_FLAG_PUBLISH))) ForceRouterAdvertisements = TRUE; // // Pick up new attributes. // We do NOT update RTE->Type. // RTE->SitePrefixLength = SitePrefixLength; RTE->ValidLifetime = ValidLifetime; RTE->PreferredLifetime = PreferredLifetime; RTE->Flags = ((Publish ? RTE_FLAG_PUBLISH : 0) | (Immortal ? RTE_FLAG_IMMORTAL : 0)); if (RTE->Preference != Pref) { RTE->Preference = Pref; InvalidateRouteCache(); } if ((OldLifetime == 0) && (ValidLifetime != 0)) { // // This route was invalid but is now valid. // InvalidateRouteCache(); } else { // // Do not check for route change notifications below. // RTE = NULL; } } break; } } // end for if (RTE != NULL) { // // This update resulted in adding or deleting a route, // so check for matching route change notifications. // CheckRtChangeNotifyRequests(&Context, FileObject, RTE); } } // end if (! IsDisabledIF(IF)) KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Delete) ExFreePool(RTE); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } return Status; } //* RouteTableResetAutoConfig // // Reset the lifetimes of all auto-configured routes for an interface. // Also resets prefixes in the Site Prefix Table. // // Callable from a thread or DPC context. // May be called with an interface lock held. // void RouteTableResetAutoConfig(Interface *IF, uint MaxLifetime) { CheckRtChangeContext Context; RouteTableEntry *RTE, **PrevRTE; SitePrefixEntry *SPE; InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); // // Reset all routes for this interface. // PrevRTE = &RouteTable.First; while ((RTE = *PrevRTE) != NULL) { if (RTE->IF == IF) { // // Is this an auto-configured route? // if (RTE->Type == RTE_TYPE_AUTOCONF) { if (MaxLifetime == 0) { // // Invalidate all cached routes. // InvalidateRouteCache(); // // Remove the RTE from the list. // RemoveRTE(PrevRTE, RTE); // // Check for matching route change notification requests. // CheckRtChangeNotifyRequests(&Context, NULL, RTE); if (IsOnLinkRTE(RTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> IF %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->IF)); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> NCE %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->NCE)); ReleaseNCE(RTE->NCE); } // // Free the RTE. // ExFreePool(RTE); continue; } if (RTE->ValidLifetime > MaxLifetime) { // // Reset the lifetime to a small value. // RTE->ValidLifetime = MaxLifetime; } } } // // Move to the next RTE. // PrevRTE = &RTE->Next; } ASSERT(PrevRTE == RouteTable.Last); // // Reset all site prefixes for this interface. // for (SPE = SitePrefixTable; SPE != NULL; SPE = SPE->Next) { if (SPE->IF == IF) { // // Is this an auto-configured site prefix? // if (SPE->ValidLifetime != INFINITE_LIFETIME) { // // Reset the lifetime to a small value. // if (SPE->ValidLifetime > MaxLifetime) SPE->ValidLifetime = MaxLifetime; } } } KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } } //* RouteTableReset // // Removes all manually-configured routing state. // // Callable from a thread or DPC context. // Called with NO locks held. // void RouteTableReset(void) { CheckRtChangeContext Context; RouteTableEntry *RTE, **PrevRTE; InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); // // Remove all manually-configured routes. // PrevRTE = &RouteTable.First; while ((RTE = *PrevRTE) != NULL) { // // Is this a manual route? // if (RTE->Type == RTE_TYPE_MANUAL) { // // Invalidate all cached routes. // InvalidateRouteCache(); // // Remove the RTE from the list. // RemoveRTE(PrevRTE, RTE); // // Check for matching route change notification requests. // CheckRtChangeNotifyRequests(&Context, NULL, RTE); if (IsOnLinkRTE(RTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> IF %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->IF)); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> NCE %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->NCE)); ReleaseNCE(RTE->NCE); } // // Free the RTE. // ExFreePool(RTE); continue; } // // Move to the next RTE. // PrevRTE = &RTE->Next; } ASSERT(PrevRTE == RouteTable.Last); KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } } //* RouteTableRemove // // Releases all routing state associated with the interface. // // Callable from a thread or DPC context. // Called with NO locks held. // void RouteTableRemove(Interface *IF) { CheckRtChangeContext Context; RouteTableEntry *RTE, **PrevRTE; RouteCacheEntry *RCE, *NextRCE; SitePrefixEntry *SPE, **PrevSPE; BindingCacheEntry *BCE, *NextBCE; KIRQL OldIrql; InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); // // Remove routes for this interface. // PrevRTE = &RouteTable.First; while ((RTE = *PrevRTE) != NULL) { if (RTE->IF == IF) { // // Remove the RTE from the list. // RemoveRTE(PrevRTE, RTE); // // Check for matching route change notification requests. // CheckRtChangeNotifyRequests(&Context, NULL, RTE); if (IsOnLinkRTE(RTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> IF %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->IF)); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> NCE %p released\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->NCE)); ReleaseNCE(RTE->NCE); } // // Free the RTE. // ExFreePool(RTE); } else { // // Move to the next RTE. // PrevRTE = &RTE->Next; } } ASSERT(PrevRTE == RouteTable.Last); // // Invalidate all cached routes. // InvalidateRouteCache(); // // Remove all site prefixes for this interface. // PrevSPE = &SitePrefixTable; while ((SPE = *PrevSPE) != NULL) { if (SPE->IF == IF) { // // Remove the SPE from the list. // *PrevSPE = SPE->Next; // // Release the SPE. // ExFreePool(SPE); } else { // // Move to the next SPE. // PrevSPE = &SPE->Next; } } KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } KeAcquireSpinLock(&RouteCacheLock, &OldIrql); // // Remove cached routes for this interface. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = NextRCE) { NextRCE = RCE->Next; if (RCE->NTE->IF == IF) { RemoveRCE(RCE); ReleaseRCE(RCE); } } // // Remove binding cache entries for this interface. // for (BCE = BindingCache.First; BCE != SentinelBCE; BCE = NextBCE) { NextBCE = BCE->Next; if (BCE->CareOfRCE->NTE->IF == IF) DestroyBCE(BCE); } KeReleaseSpinLock(&RouteCacheLock, OldIrql); } //* RouteTableTimeout // // Called periodically from IPv6Timeout. // Handles lifetime expiration of routing table entries. // void RouteTableTimeout(void) { CheckRtChangeContext Context; RouteTableEntry *RTE, **PrevRTE; #if DBG RouteCacheEntry *RCE; uint RCECount; #endif InitCheckRtChangeContext(&Context); KeAcquireSpinLock(&RouteTableLock, &Context.OldIrql); PrevRTE = &RouteTable.First; while ((RTE = *PrevRTE) != NULL) { // // First decrement the preferred lifetime. // if (!(RTE->Flags & RTE_FLAG_IMMORTAL) && (RTE->PreferredLifetime != 0) && (RTE->PreferredLifetime != INFINITE_LIFETIME)) RTE->PreferredLifetime--; // // Now check the valid lifetime. // If the valid lifetime is zero, then // the route is not valid and is not used. // We delete invalid routes, unless they are published. // if (RTE->ValidLifetime == 0) { // // This is an invalid route, only kept around // for purposes of generating Router Advertisements // or because it is a system route. // ASSERT((RTE->Flags & RTE_FLAG_PUBLISH) || (RTE->Type == RTE_TYPE_SYSTEM)); } else if (!(RTE->Flags & RTE_FLAG_IMMORTAL) && (RTE->ValidLifetime != INFINITE_LIFETIME) && (--RTE->ValidLifetime == 0)) { // // The route is now invalid. // Invalidate all cached routes. // InvalidateRouteCache(); // // Check for matching route change notification requests. // CheckRtChangeNotifyRequests(&Context, NULL, RTE); if (!(RTE->Flags & RTE_FLAG_PUBLISH) && (RTE->Type != RTE_TYPE_SYSTEM)) { // // Remove the RTE from the list. // See similar code in RouteTableUpdate. // RemoveRTE(PrevRTE, RTE); if (IsOnLinkRTE(RTE)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> IF %p timed out\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->IF)); } else { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "Route RTE %p %s/%u -> NCE %p timed out\n", RTE, FormatV6Address(&RTE->Prefix), RTE->PrefixLength, RTE->NCE)); ReleaseNCE(RTE->NCE); } // // Release the RTE and continue to the next RTE. // ExFreePool(RTE); continue; } } // // Continue to the next RTE. // PrevRTE = &RTE->Next; } ASSERT(PrevRTE == RouteTable.Last); KeReleaseSpinLock(&RouteTableLock, Context.OldIrql); if (Context.RequestList != NULL) { // // Complete the pending route change notifications. // CompleteRtChangeNotifyRequests(&Context); } } //* SitePrefixUpdate // // Updates the site prefix table by creating a new site prefix // or modifying the lifetime of an existing site prefix. // // Callable from a thread or DPC context. // May be called with an interface lock held. // void SitePrefixUpdate( Interface *IF, const IPv6Addr *SitePrefix, uint SitePrefixLength, uint ValidLifetime) { IPv6Addr Prefix; SitePrefixEntry *SPE, **PrevSPE; KIRQL OldIrql; // // Ensure that the unused prefix bits are zero. // This makes the prefix comparisons below safe. // CopyPrefix(&Prefix, SitePrefix, SitePrefixLength); KeAcquireSpinLock(&RouteTableLock, &OldIrql); // // Search for an existing Site Prefix Entry. // for (PrevSPE = &SitePrefixTable; ; PrevSPE = &SPE->Next) { SPE = *PrevSPE; if (SPE == NULL) { // // No existing entry for this prefix. // Create an entry if the lifetime is non-zero. // if (ValidLifetime != 0) { SPE = ExAllocatePool(NonPagedPool, sizeof *SPE); if (SPE == NULL) break; SPE->IF = IF; SPE->Prefix = Prefix; SPE->SitePrefixLength = SitePrefixLength; SPE->ValidLifetime = ValidLifetime; // // Add the new entry to the table. // SPE->Next = SitePrefixTable; SitePrefixTable = SPE; } break; } if ((SPE->IF == IF) && IP6_ADDR_EQUAL(&SPE->Prefix, &Prefix) && (SPE->SitePrefixLength == SitePrefixLength)) { // // We have an existing site prefix. // Remove the prefix if the new lifetime is zero, // otherwise update the prefix. // if (ValidLifetime == 0) { // // Remove the SPE from the list. // See similar code in SitePrefixTimeout. // *PrevSPE = SPE->Next; // // Release the SPE. // ExFreePool(SPE); } else { // // Pick up new attributes. // SPE->ValidLifetime = ValidLifetime; } break; } } KeReleaseSpinLock(&RouteTableLock, OldIrql); } //* SitePrefixMatch // // Checks the destination address against // the prefixes in the Site Prefix Table. // If there is a match, returns the site identifier // associated with the matching prefix. // If there is no match, returns zero. // // Callable from a thread or DPC context. // Called with NO locks held. // uint SitePrefixMatch(const IPv6Addr *Destination) { SitePrefixEntry *SPE; KIRQL OldIrql; uint MatchingSite = 0; KeAcquireSpinLock(&RouteTableLock, &OldIrql); for (SPE = SitePrefixTable; SPE != NULL; SPE = SPE->Next) { // // Does this site prefix match the destination address? // if (HasPrefix(Destination, &SPE->Prefix, SPE->SitePrefixLength)) { // // We have found a matching site prefix. // No need to look further. // MatchingSite = SPE->IF->ZoneIndices[ADE_SITE_LOCAL]; break; } } KeReleaseSpinLock(&RouteTableLock, OldIrql); return MatchingSite; } //* SitePrefixTimeout // // Called periodically from IPv6Timeout. // Handles lifetime expiration of site prefixes. // void SitePrefixTimeout(void) { SitePrefixEntry *SPE, **PrevSPE; KeAcquireSpinLockAtDpcLevel(&RouteTableLock); PrevSPE = &SitePrefixTable; while ((SPE = *PrevSPE) != NULL) { if (SPE->ValidLifetime == 0) { // // Remove the SPE from the list. // *PrevSPE = SPE->Next; // // Release the SPE. // ExFreePool(SPE); } else { if (SPE->ValidLifetime != INFINITE_LIFETIME) SPE->ValidLifetime--; PrevSPE = &SPE->Next; } } KeReleaseSpinLockFromDpcLevel(&RouteTableLock); } //* ConfirmForwardReachability - tell ND that packets are getting through. // // Upper layers call this routine upon receiving acknowledgements that // data sent by this node has arrived recently at the peer represented // by this RCE. Such acknowledgements are considered to be proof of // forward reachability for the purposes of Neighbor Discovery. // // Caller should be holding a reference on the RCE. // Callable from a thread or DPC context. // void ConfirmForwardReachability(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; // CareOfRCE, if any, for this route. NeighborCacheEntry *NCE; // First-hop neighbor for this route. CareOfRCE = GetCareOfRCE(RCE); NCE = (CareOfRCE ? CareOfRCE : RCE)->NCE; NeighborCacheReachabilityConfirmation(NCE); if (CareOfRCE != NULL) ReleaseRCE(CareOfRCE); } //* ForwardReachabilityInDoubt - tell ND we're dubious. // // Upper layers call this routine when they don't receive acknowledgements // which they'd otherwise expect if the peer represented by this RCE was // still reachable. This calls into question whether the first-hop // might be the problem, so we tell ND that we're suspicious of its // reachable status. // // Caller should be holding a reference on the RCE. // Callable from a thread or DPC context. // void ForwardReachabilityInDoubt(RouteCacheEntry *RCE) { RouteCacheEntry *CareOfRCE; // CareOfRCE, if any, for this route. NeighborCacheEntry *NCE; // First-hop neighbor for this route. Interface *IF; // Interface we use to reach this neighbor. KIRQL OldIrql; // Temporary place to stash the interrupt level. CareOfRCE = GetCareOfRCE(RCE); NCE = (CareOfRCE ? CareOfRCE : RCE)->NCE; NeighborCacheReachabilityInDoubt(NCE); if (CareOfRCE != NULL) ReleaseRCE(CareOfRCE); } //* GetPathMTUFromRCE - lookup MTU to use in sending on this route. // // Get the PathMTU from an RCE. // // Note that PathMTU is volatile unless the RouteCacheLock // is held. Furthermore the Interface's LinkMTU may have changed // since the RCE was created, due to a Router Advertisement. // (LinkMTU is always volatile.) // // Callable from a thread or DPC context. // Called with NO locks held. // uint GetPathMTUFromRCE(RouteCacheEntry *RCE) { uint PathMTU, LinkMTU; KIRQL OldIrql; LinkMTU = RCE->NCE->IF->LinkMTU; PathMTU = RCE->PathMTU; // // We lazily check to see if it's time to probe for an increased Path // MTU as this is perceived to be cheaper than routinely running through // all our RCEs looking for one whose PMTU timer has expired. // if ((RCE->PMTULastSet != 0) && ((uint)(IPv6TickCount - RCE->PMTULastSet) >= PATH_MTU_RETRY_TIME)) { // // It's been at least 10 minutes since we last lowered our PMTU // as the result of receiving a Path Too Big message. Bump it // back up to the Link MTU to see if the path is larger now. // KeAcquireSpinLock(&RouteCacheLock, &OldIrql); PathMTU = RCE->PathMTU = LinkMTU; RCE->PMTULastSet = 0; KeReleaseSpinLock(&RouteCacheLock, OldIrql); } // // We lazily check to see if our Link MTU has shrunk below our Path MTU, // as this is perceived to be cheaper than running through all our RCEs // looking for a too big Path MTU when a Link MTU shrinks. // // REVIEW: A contrarian might point out that Link MTUs rarely (if ever) // REVIEW: shrink, whereas we do this check on every packet sent. // if (PathMTU > LinkMTU) { KeAcquireSpinLock(&RouteCacheLock, &OldIrql); LinkMTU = RCE->NCE->IF->LinkMTU; PathMTU = RCE->PathMTU; if (PathMTU > LinkMTU) { PathMTU = RCE->PathMTU = LinkMTU; RCE->PMTULastSet = 0; } KeReleaseSpinLock(&RouteCacheLock, OldIrql); } return PathMTU; } //* GetEffectivePathMTUFromRCE // // Adjust the true path MTU to account for mobility and fragment headers. // Determines PMTU available to upper layer protocols. // // Callable from a thread or DPC context. // Called with NO locks held. // uint GetEffectivePathMTUFromRCE(RouteCacheEntry *RCE) { uint PathMTU; KIRQL OldIrql; RouteCacheEntry *CareOfRCE; CareOfRCE = GetCareOfRCE(RCE); PathMTU = GetPathMTUFromRCE(CareOfRCE ? CareOfRCE : RCE); if (PathMTU == 0) { // // We need to leave space for a fragment header in all // packets we send to this destination. // PathMTU = IPv6_MINIMUM_MTU - sizeof(FragmentHeader); } if (CareOfRCE != NULL) { // // Mobility is in effect for this destination. // Leave space for routing header. // PathMTU -= sizeof(IPv6RoutingHeader) + sizeof(IPv6Addr); ReleaseRCE(CareOfRCE); } return PathMTU; } //* UpdatePathMTU // // Update the route cache with a new MTU obtained // from a Packet Too Big message. Returns TRUE if this // update modified a PMTU value we had cached previously. // // Callable from DPC context, not from thread context. // Called with NO locks held. // int UpdatePathMTU( Interface *IF, const IPv6Addr *Dest, uint MTU) { RouteCacheEntry *RCE; uint Now; int Changed = FALSE; KeAcquireSpinLockAtDpcLevel(&RouteCacheLock); // // Search the route cache for the appropriate RCE. // There will be at most one. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = RCE->Next) { if (IP6_ADDR_EQUAL(&RCE->Destination, Dest) && (RCE->NTE->IF == IF)) { // // Update the path MTU. // We never actually lower the path MTU below IPv6_MINIMUM_MTU. // If this is requested, we instead start including fragment // headers in all packets but we still use IPv6_MINIMUM_MTU. // if (MTU < RCE->PathMTU) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "UpdatePathMTU(RCE %p): new MTU %u for %s\n", RCE, MTU, FormatV6Address(Dest))); if (MTU < IPv6_MINIMUM_MTU) RCE->PathMTU = 0; // Always include fragment header. else RCE->PathMTU = MTU; Changed = TRUE; // // Timestamp it (starting the timer). // A zero value means no timer, so don't use it. // Now = IPv6TickCount; if (Now == 0) Now = 1; RCE->PMTULastSet = Now; } break; } } KeReleaseSpinLockFromDpcLevel(&RouteCacheLock); return Changed; } //* RedirectRouteCache // // Update the route cache to reflect a Redirect message. // // Callable from DPC context, not from thread context. // Called with NO locks held. // IP_STATUS // Returns: IP_SUCCESS if redirect was legit, otherwise failure. RedirectRouteCache( const IPv6Addr *Source, // Source of the redirect. const IPv6Addr *Dest, // Destination that is being redirected. Interface *IF, // Interface that this all applies to. NeighborCacheEntry *NCE) // New router for the destination. { RouteCacheEntry *RCE; ushort DestScope; uint DestScopeId; IP_STATUS ReturnValue; #if DBG char Buffer1[INET6_ADDRSTRLEN], Buffer2[INET6_ADDRSTRLEN]; FormatV6AddressWorker(Buffer1, Dest); FormatV6AddressWorker(Buffer2, &NCE->NeighborAddress); #endif // // Our caller guarantees this. // ASSERT(IF == NCE->IF); DestScope = AddressScope(Dest); DestScopeId = IF->ZoneIndices[DestScope]; KeAcquireSpinLockAtDpcLevel(&RouteCacheLock); // // Get the current RCE for this destination. // ReturnValue = FindOrCreateRoute(Dest, DestScopeId, IF, &RCE); if (ReturnValue == IP_SUCCESS) { // // We must check that the source of the redirect // is the current next-hop neighbor. // (This is a simple sanity check - it does not // prevent clever neighbors from hijacking.) // if (!IP6_ADDR_EQUAL(&RCE->NCE->NeighborAddress, Source)) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR, "RedirectRouteCache(dest %s -> %s): hijack from %s\n", Buffer1, Buffer2, FormatV6Address(Source))); ReturnValue = IP_GENERAL_FAILURE; } else if (RCE->RefCnt == 2) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "RedirectRouteCache(dest %s -> %s): inplace %p\n", Buffer1, Buffer2, RCE)); // // There are no references to this RCE outside // of the cache, so we can update it in place. // ReleaseNCE(RCE->NCE); // // It's still OK to compare against RCE->NCE. // goto UpdateRCE; } else { RouteCacheEntry *NewRCE; // // Create a new route cache entry for the redirect. // CreateOrReuseRoute will not return RCE, // because we have an extra reference for it. // NewRCE = CreateOrReuseRoute(); if (NewRCE != NULL) { ASSERT(NewRCE != RCE); KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "RedirectRouteCache(dest %s -> %s): old %p new %p\n", Buffer1, Buffer2, RCE, NewRCE)); // // Copy the RCE and fix up references. // We must copy the correct validation counter value. // *NewRCE = *RCE; NewRCE->RefCnt = 2; // One for the cache, one to release below. // We do not AddRefNCE(NewRCE->NCE), see below. AddRefNTE(NewRCE->NTE); // // Cause anyone who is using/caching the old RCE to notice // that it is no longer valid, so they will find the new RCE. // Then remove the old RCE from the cache. // RCE->Valid--; // RouteCacheValidationCounter increases. RemoveRCE(RCE); ReleaseRCE(RCE); // The cache's reference. ReleaseRCE(RCE); // Reference from FindOrCreateRoute. // // Add the new route cache entry to the cache. // InsertRCE(NewRCE); RCE = NewRCE; UpdateRCE: RCE->Type = RCE_TYPE_REDIRECT; if (RCE->NCE != NCE) { // // Reset PMTU discovery. // RCE->PathMTU = IF->LinkMTU; RCE->PMTULastSet = 0; } // // At this point, RCE->NCE does NOT hold a reference. // AddRefNCE(NCE); RCE->NCE = NCE; } else { // // Could not allocate a new RCE. // REVIEW - Remove the old RCE from the cache? // ReturnValue = IP_NO_RESOURCES; } } // // Release our references. // ReleaseRCE(RCE); } KeReleaseSpinLockFromDpcLevel(&RouteCacheLock); return ReturnValue; } //* InitRouting - Initialize the routing module. // void InitRouting(void) { KeInitializeSpinLock(&RouteCacheLock); KeInitializeSpinLock(&RouteTableLock); // RouteCache.Limit initialized in ConfigureGlobalParameters. RouteCache.First = RouteCache.Last = SentinelRCE; RouteTable.First = NULL; RouteTable.Last = &RouteTable.First; // BindingCache.Limit initialized in ConfigureGlobalParameters. BindingCache.First = BindingCache.Last = SentinelBCE; InitializeListHead(&RouteNotifyQueue); } //* UnloadRouting // // Called when IPv6 stack is unloading. // void UnloadRouting(void) { // // With all the interfaces destroyed, // there should be no routes left. // ASSERT(RouteTable.First == NULL); ASSERT(RouteTable.Last == &RouteTable.First); ASSERT(RouteCache.First == SentinelRCE); ASSERT(RouteCache.Last == SentinelRCE); ASSERT(BindingCache.First == SentinelBCE); ASSERT(BindingCache.Last == SentinelBCE); // // Irps hold references for our device object, // so pending notification requests prevent // us from unloading. // ASSERT(RouteNotifyQueue.Flink == RouteNotifyQueue.Blink); } //* InsertBCE // // Insert the BCE in the binding cache. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void InsertBCE(BindingCacheEntry *BCE) { BindingCacheEntry *AfterBCE = SentinelBCE; RouteCacheEntry *RCE; BCE->Prev = AfterBCE; (BCE->Next = AfterBCE->Next)->Prev = BCE; AfterBCE->Next = BCE; BindingCache.Count++; // // Update any existing RCEs to point to this BCE. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = RCE->Next) { if (IP6_ADDR_EQUAL(&RCE->Destination, &BCE->HomeAddr)) RCE->BCE = BCE; } } //* RemoveBCE // // Remove the BCE from the binding cache. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void RemoveBCE(BindingCacheEntry *BCE) { RouteCacheEntry *RCE; BCE->Prev->Next = BCE->Next; BCE->Next->Prev = BCE->Prev; BindingCache.Count--; // // Remove any references to this BCE from the route cache. // for (RCE = RouteCache.First; RCE != SentinelRCE; RCE = RCE->Next) { if (RCE->BCE == BCE) RCE->BCE = NULL; } } //* MoveToFrontBCE // // Move an BCE to the front of the list. // // Called with the route cache lock held. // Callable from a thread or DPC context. // void MoveToFrontBCE(BindingCacheEntry *BCE) { if (BCE->Prev != SentinelBCE) { BindingCacheEntry *AfterBCE = SentinelBCE; // // Remove the BCE from its current location. // BCE->Prev->Next = BCE->Next; BCE->Next->Prev = BCE->Prev; // // And put it at the front. // BCE->Prev = AfterBCE; (BCE->Next = AfterBCE->Next)->Prev = BCE; AfterBCE->Next = BCE; } } //* CreateBindingCacheEntry - create new BCE. // // Allocates a new Binding Cache entry. // Returns NULL if a new BCE can not be allocated. // // Must be called with the RouteCache lock held. // BindingCacheEntry * CreateOrReuseBindingCacheEntry() { BindingCacheEntry *BCE; if (BindingCache.Count >= BindingCache.Limit) { // // Reuse the BCE at the end of the list. // BCE = BindingCache.Last; RemoveBCE(BCE); ReleaseRCE(BCE->CareOfRCE); } else { // // Allocate a new BCE. // BCE = ExAllocatePool(NonPagedPool, sizeof *BCE); } return BCE; } //* DestroyBCE - remove an entry from the BindingCache. // // Must be called with the RouteCache lock held. // void DestroyBCE(BindingCacheEntry *BCE) { // // Unchain the given BCE and destroy it. // RemoveBCE(BCE); ReleaseRCE(BCE->CareOfRCE); ExFreePool(BCE); } //* FindBindingCacheEntry // // Looks for a binding cache entry with the specified care-of address. // Must be called with the route cache lock held. // BindingCacheEntry * FindBindingCacheEntry(const IPv6Addr *HomeAddr) { BindingCacheEntry *BCE; for (BCE = BindingCache.First; ; BCE = BCE->Next) { if (BCE == SentinelBCE) { // // Did not find a matching entry. // BCE = NULL; break; } if (IP6_ADDR_EQUAL(&BCE->HomeAddr, HomeAddr)) { // // Found a matching entry. // break; } } return BCE; } //* CacheBindingUpdate - update the binding cache entry for an address. // // Find or Create (if necessary) an RCE to the CareOfAddress. This routine // is called in response to a Binding Cache Update. // // Callable from DPC context, not from thread context. // Called with NO locks held. // BindingUpdateDisposition // Returns: Binding Ack Status code. CacheBindingUpdate( IPv6BindingUpdateOption UNALIGNED *BindingUpdate, const IPv6Addr *CareOfAddr, // Address to use for mobile node. NetTableEntryOrInterface *NTEorIF, // NTE or IF receiving the BU. const IPv6Addr *HomeAddr) // Mobile node's home address. { BindingCacheEntry *BCE; BindingUpdateDisposition ReturnValue = IPV6_BINDING_ACCEPTED; IP_STATUS Status; int DeleteRequest; // Request is to delete an existing binding? ushort SeqNo; RouteCacheEntry *CareOfRCE; ushort CareOfScope; uint CareOfScopeId; // // Note that we assume the care-of address is scoped // to the receiving interface, even when // the care-of address is present in a suboption // instead of the IPv6 source address field. // CareOfScope = AddressScope(CareOfAddr); CareOfScopeId = NTEorIF->IF->ZoneIndices[CareOfScope]; // // Is this Binding Update a request to remove entries // from our binding cache? // DeleteRequest = ((BindingUpdate->Lifetime == 0) || IP6_ADDR_EQUAL(HomeAddr, CareOfAddr)); SeqNo = net_short(BindingUpdate->SeqNumber); KeAcquireSpinLockAtDpcLevel(&RouteCacheLock); // // Search the binding cache for the home address. // for (BCE = BindingCache.First; BCE != SentinelBCE; BCE = BCE->Next) { if (!IP6_ADDR_EQUAL(&BCE->HomeAddr, HomeAddr)) continue; // // We've found an existing entry for this home address. // Verify the sequence number is greater than the cached binding's // sequence number if there is one. // if ((short)(SeqNo - BCE->BindingSeqNumber) <= 0) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR, "CacheBindingUpdate: New sequence number too small " "(old seqnum = %d, new seqnum = %d)\n", BCE->BindingSeqNumber, SeqNo)); ReturnValue = IPV6_BINDING_SEQ_NO_TOO_SMALL; goto Return; } // // If the request is to delete the entry, do so and return. // if (DeleteRequest) { DestroyBCE(BCE); goto Return; } // // Update the binding. // BCE->BindingLifetime = ConvertSecondsToTicks(net_long(BindingUpdate->Lifetime)); BCE->BindingSeqNumber = SeqNo; CareOfRCE = BCE->CareOfRCE; // // If the care-of address or scope-id has changed, // then we need to create a new care-of RCE. // if (!IP6_ADDR_EQUAL(&CareOfRCE->Destination, CareOfAddr) || (CareOfScopeId != CareOfRCE->NTE->IF->ZoneIndices[CareOfScope])) { RouteCacheEntry *NewRCE; // // Note that since we already hold the RouteCacheLock we // call FindOrCreateRoute here instead of RouteToDestination. // Status = FindOrCreateRoute(CareOfAddr, CareOfScopeId, NULL, &NewRCE); if (Status == IP_SUCCESS) { // // Update the binding cache entry. // ReleaseRCE(CareOfRCE); BCE->CareOfRCE = NewRCE; } else { // // Because we could not update the BCE, // destroy it. // DestroyBCE(BCE); if (Status == IP_NO_RESOURCES) ReturnValue = IPV6_BINDING_NO_RESOURCES; else ReturnValue = IPV6_BINDING_REJECTED; } } goto Return; } if (DeleteRequest) { // // We're done. // goto Return; } // // We want to cache a binding and did not find an existing binding // for the home address above. So we create a new binding cache entry. // BCE = CreateOrReuseBindingCacheEntry(); if (BCE == NULL) { ReturnValue = IPV6_BINDING_NO_RESOURCES; goto Return; } BCE->HomeAddr = *HomeAddr; BCE->BindingLifetime = ConvertSecondsToTicks(net_long(BindingUpdate->Lifetime)); BCE->BindingSeqNumber = SeqNo; // // Now create a new RCE for the care-of address. // Note that since we already hold the RouteCacheLock we // call FindOrCreateRoute here instead of RouteToDestination. // Status = FindOrCreateRoute(CareOfAddr, CareOfScopeId, NULL, &BCE->CareOfRCE); if (Status != IP_SUCCESS) { // // Couldn't get a route. // ExFreePool(BCE); if (Status == IP_NO_RESOURCES) ReturnValue = IPV6_BINDING_NO_RESOURCES; else ReturnValue = IPV6_BINDING_REJECTED; } else { // // Now that the BCE is fully initialized, // add it to the cache. This also updates existing RCEs. // InsertBCE(BCE); } Return: KeReleaseSpinLockFromDpcLevel(&RouteCacheLock); return ReturnValue; } //* BindingCacheTimeout // // Check for and handle binding cache lifetime expirations. // // Callable from DPC context, not from thread context. // Called with NO locks held. // void BindingCacheTimeout(void) { BindingCacheEntry *BCE, *NextBCE; KeAcquireSpinLockAtDpcLevel(&RouteCacheLock); // // Search the route cache for all binding cache entries. Update // their lifetimes, and remove if expired. // for (BCE = BindingCache.First; BCE != SentinelBCE; BCE = NextBCE) { NextBCE = BCE->Next; // // REVIEW: The mobile IPv6 spec allows correspondent nodes to // REVIEW: send a Binding Request when the current binding's // REVIEW: lifetime is "close to expiration" in order to prevent // REVIEW: the overhead of establishing a new binding after the // REVIEW: current one expires. For now, we just let the binding // REVIEW: expire. // if (--BCE->BindingLifetime == 0) { // // This binding cache entry has expired. // Remove it from the Binding Cache. // DestroyBCE(BCE); } } KeReleaseSpinLockFromDpcLevel(&RouteCacheLock); } //* RouterAdvertSend // // Sends a Router Advertisement. // The advert is always sent to the all-nodes multicast address. // Chooses a valid source address for the interface. // // Called with NO locks held. // Callable from DPC context, not from thread context. // // REVIEW - Should this function be in route.c or neighbor.c? Or split up? // void RouterAdvertSend( Interface *IF, // Interface on which to send. const IPv6Addr *Source, // Source address to use. const IPv6Addr *Dest) // Destination address to use. { NDIS_STATUS Status; NDIS_PACKET *Packet; NDIS_BUFFER *Buffer; uint PayloadLength; uint Offset; void *Mem, *MemLeft; uint MemLen, MemLenLeft; uint SourceOptionLength; IPv6Header UNALIGNED *IP; ICMPv6Header UNALIGNED *ICMP; NDRouterAdvertisement UNALIGNED *RA; void *SourceOption; NDOptionMTU UNALIGNED *MTUOption; void *LLDest; KIRQL OldIrql; int Forwards; uint LinkMTU; uint RouterLifetime; uint DefaultRoutePreference; RouteTableEntry *RTE; ICMPv6OutStats.icmps_msgs++; // // For consistency, capture some volatile // information in locals. // Forwards = IF->Flags & IF_FLAG_FORWARDS; LinkMTU = IF->LinkMTU; Offset = IF->LinkHeaderSize; // // Allocate a buffer for the advertisement. // We typically do not use the entire buffer, // but briefly allocating a large buffer is OK. // MemLen = Offset + LinkMTU; Mem = ExAllocatePool(NonPagedPool, MemLen); if (Mem == NULL) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RouterAdvertSend - no memory?\n")); ICMPv6OutStats.icmps_errors++; return; } // // Prepare IP header of the advertisement. // We fill in the PayloadLength later. // IP = (IPv6Header UNALIGNED *)((uchar *)Mem + Offset); IP->VersClassFlow = IP_VERSION; IP->NextHeader = IP_PROTOCOL_ICMPv6; IP->HopLimit = 255; IP->Source = *Source; IP->Dest = *Dest; // // Prepare ICMP header. // ICMP = (ICMPv6Header UNALIGNED *)(IP + 1); ICMP->Type = ICMPv6_ROUTER_ADVERT; ICMP->Code = 0; ICMP->Checksum = 0; // // Prepare the Router Advertisement header. // We fill in RouterLifetime and DefaultRoutePreference later. // RA = (NDRouterAdvertisement UNALIGNED *)(ICMP + 1); RtlZeroMemory(RA, sizeof *RA); MemLeft = (void *)(RA + 1); if (IF->WriteLLOpt != NULL) { // // Include source link-layer address option if ND is enabled. // SourceOption = MemLeft; SourceOptionLength = (IF->LinkAddressLength + 2 + 7) &~ 7; ((uchar *)SourceOption)[0] = ND_OPTION_SOURCE_LINK_LAYER_ADDRESS; ((uchar *)SourceOption)[1] = SourceOptionLength >> 3; (*IF->WriteLLOpt)(IF->LinkContext, SourceOption, IF->LinkAddress); MemLeft = (uchar *)SourceOption + SourceOptionLength; } // // Always include MTU option. // MTUOption = (NDOptionMTU UNALIGNED *)MemLeft; MTUOption->Type = ND_OPTION_MTU; MTUOption->Length = 1; MTUOption->Reserved = 0; MTUOption->MTU = net_long(LinkMTU); // // OK, how much space is left? // MemLeft = (void *)(MTUOption + 1); MemLenLeft = MemLen - (uint)((uchar *)MemLeft - (uchar *)Mem); // // Now we scan the routing table looking for published routes. // We incrementally add Prefix Information and Route Information options, // and we determine RouterLifetime and DefaultRoutePreference. // RouterLifetime = 0; DefaultRoutePreference = (uint) -1; KeAcquireSpinLock(&RouteTableLock, &OldIrql); for (RTE = RouteTable.First; RTE != NULL; RTE = RTE->Next) { // // We only advertise published routes. // if (RTE->Flags & RTE_FLAG_PUBLISH) { uint Life; // In seconds. ushort PrefixScope = AddressScope(&RTE->Prefix); // // IoctlUpdateRouteTable guarantees this. // ASSERT(! IsLinkLocal(&RTE->Prefix)); if (IsOnLinkRTE(RTE) && (RTE->IF == IF)) { NDOptionPrefixInformation UNALIGNED *Prefix; // // We generate a prefix-information option // with the L and possibly the A bits set. // if (MemLenLeft < sizeof *Prefix) break; // No room for more options. Prefix = (NDOptionPrefixInformation *)MemLeft; (uchar *)MemLeft += sizeof *Prefix; MemLenLeft -= sizeof *Prefix; Prefix->Type = ND_OPTION_PREFIX_INFORMATION; Prefix->Length = 4; Prefix->PrefixLength = (uchar)RTE->PrefixLength; Prefix->Flags = ND_PREFIX_FLAG_ON_LINK; if (RTE->PrefixLength == 64) Prefix->Flags |= ND_PREFIX_FLAG_AUTONOMOUS; Prefix->Reserved2 = 0; Prefix->Prefix = RTE->Prefix; // // Is this also a site prefix? // NB: The SitePrefixLength field overlaps Reserved2. // if (RTE->SitePrefixLength != 0) { Prefix->Flags |= ND_PREFIX_FLAG_SITE_PREFIX; Prefix->SitePrefixLength = (uchar)RTE->SitePrefixLength; } // // ConvertTicksToSeconds preserves the infinite value. // Life = net_long(ConvertTicksToSeconds(RTE->ValidLifetime)); Prefix->ValidLifetime = Life; Life = net_long(ConvertTicksToSeconds(RTE->PreferredLifetime)); Prefix->PreferredLifetime = Life; } else if (Forwards && (RTE->IF != IF) && (IF->ZoneIndices[PrefixScope] == RTE->IF->ZoneIndices[PrefixScope])) { // // We only advertise routes if we are forwarding // and if we won't forward out the same interface: // if such a router were published and used, // we'd generate a Redirect, but better to avoid // in the first place. // Also, we keep scoped routes within their zone. // if (RTE->PrefixLength == 0) { // // We don't explicitly advertise zero-length prefixes. // Instead we advertise a non-zero router lifetime. // if (RTE->ValidLifetime > RouterLifetime) RouterLifetime = RTE->ValidLifetime; if (RTE->Preference < DefaultRoutePreference) DefaultRoutePreference = RTE->Preference; } else { NDOptionRouteInformation UNALIGNED *Route; uint OptionSize; // // We generate a route-information option. // if (RTE->PrefixLength <= 64) OptionSize = 16; else OptionSize = 24; if (MemLenLeft < OptionSize) break; // No room for more options. Route = (NDOptionRouteInformation *)MemLeft; (uchar *)MemLeft += OptionSize; MemLenLeft -= OptionSize; Route->Type = ND_OPTION_ROUTE_INFORMATION; Route->Length = OptionSize >> 3; Route->PrefixLength = (uchar)RTE->PrefixLength; Route->Flags = EncodeRoutePreference(RTE->Preference); RtlCopyMemory(&Route->Prefix, &RTE->Prefix, OptionSize - 8); // // ConvertTicksToSeconds preserves the infinite value. // Life = net_long(ConvertTicksToSeconds(RTE->ValidLifetime)); Route->RouteLifetime = Life; } } } } KeReleaseSpinLock(&RouteTableLock, OldIrql); if (RouterLifetime != 0) { // // We will be a default router. Calculate the 16-bit lifetime. // Note that there is no infinite value on the wire. // RouterLifetime = ConvertTicksToSeconds(RouterLifetime); if (RouterLifetime > 0xffff) RouterLifetime = 0xffff; RA->RouterLifetime = net_short((ushort)RouterLifetime); RA->Flags = EncodeRoutePreference(DefaultRoutePreference); } // // Calculate a payload length for the advertisement. // PayloadLength = (uint)((uchar *)MemLeft - (uchar *)ICMP); IP->PayloadLength = net_short((ushort)PayloadLength); // // Now allocate and initialize an NDIS packet and buffer. // This is much like IPv6AllocatePacket, // except we already have the memory. // NdisAllocatePacket(&Status, &Packet, IPv6PacketPool); if (Status != NDIS_STATUS_SUCCESS) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RouterAdvertSend - couldn't allocate header!?!\n")); ExFreePool(Mem); ICMPv6OutStats.icmps_errors++; return; } NdisAllocateBuffer(&Status, &Buffer, IPv6BufferPool, Mem, Offset + sizeof(IPv6Header) + PayloadLength); if (Status != NDIS_STATUS_SUCCESS) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR, "RouterAdvertSend - couldn't allocate buffer!?!\n")); NdisFreePacket(Packet); ExFreePool(Mem); ICMPv6OutStats.icmps_errors++; return; } InitializeNdisPacket(Packet); PC(Packet)->CompletionHandler = IPv6PacketComplete; NdisChainBufferAtFront(Packet, Buffer); // // Calculate the ICMPv6 checksum. It covers the entire ICMPv6 message // starting with the ICMPv6 header, plus the IPv6 pseudo-header. // ICMP->Checksum = ChecksumPacket( Packet, Offset + sizeof *IP, NULL, PayloadLength, AlignAddr(&IP->Source), AlignAddr(&IP->Dest), IP_PROTOCOL_ICMPv6); ASSERT(ICMP->Checksum != 0); // // Calculate the link-level destination address. // (The IPv6 destination is a multicast address.) // We prevent loopback of all ND packets. // LLDest = alloca(IF->LinkAddressLength); (*IF->ConvertAddr)(IF->LinkContext, AlignAddr(&IP->Dest), LLDest); PC(Packet)->Flags = NDIS_FLAGS_MULTICAST_PACKET | NDIS_FLAGS_DONT_LOOPBACK; // // Before we transmit the packet (and lose ownership of the memory), // make a pass over the packet, processing the options ourselves. // This is like receiving our own RA, except we do not create routes. // The options are well-formed of course. // Mem = (void *)(MTUOption + 1); while (Mem < MemLeft) { if (((uchar *)Mem)[0] == ND_OPTION_PREFIX_INFORMATION) { NDOptionPrefixInformation UNALIGNED *Prefix = (NDOptionPrefixInformation UNALIGNED *)Mem; uint ValidLifetime, PreferredLifetime; // // Because we just constructed the prefix-information options, // we know they are syntactically valid. // ValidLifetime = net_long(Prefix->ValidLifetime); ValidLifetime = ConvertSecondsToTicks(ValidLifetime); PreferredLifetime = net_long(Prefix->PreferredLifetime); PreferredLifetime = ConvertSecondsToTicks(PreferredLifetime); if ((IF->CreateToken != NULL) && (Prefix->Flags & ND_PREFIX_FLAG_AUTONOMOUS)) { NetTableEntry *NTE; // // IoctlUpdateRouteTable only allows "proper" prefixes // to be published. // ASSERT(!IsLinkLocal(AlignAddr(&Prefix->Prefix))); ASSERT(!IsMulticast(AlignAddr(&Prefix->Prefix))); ASSERT(Prefix->PrefixLength == 64); // // Perform stateless address autoconfiguration for this prefix. // AddrConfUpdate(IF, AlignAddr(&Prefix->Prefix), ValidLifetime, PreferredLifetime, TRUE, // Authenticated. &NTE); if (NTE != NULL) { IPv6Addr NewAddr; // // Create the subnet anycast address for this prefix, // if we created a corresponding unicast address. // CopyPrefix(&NewAddr, AlignAddr(&Prefix->Prefix), 64); (void) FindOrCreateAAE(IF, &NewAddr, CastFromNTE(NTE)); ReleaseNTE(NTE); } } if (Prefix->Flags & ND_PREFIX_FLAG_SITE_PREFIX) { // // Again, IoctlUpdateRouteTable enforces sanity checks. // ASSERT(!IsSiteLocal(AlignAddr(&Prefix->Prefix))); ASSERT(Prefix->SitePrefixLength <= Prefix->PrefixLength); ASSERT(Prefix->SitePrefixLength != 0); SitePrefixUpdate(IF, AlignAddr(&Prefix->Prefix), Prefix->SitePrefixLength, ValidLifetime); } } (uchar *)Mem += ((uchar *)Mem)[1] << 3; } // // Transmit the packet. // ICMPv6OutStats.icmps_typecount[ICMPv6_ROUTER_ADVERT]++; IPv6SendLL(IF, Packet, Offset, LLDest); } //* GetBestRouteInfo // // Calculates the best source address and outgoing interface // for the specified destination address. // IP_STATUS GetBestRouteInfo( const IPv6Addr *Addr, ulong ScopeId, IP6RouteEntry *Ire) { IP_STATUS Status; RouteCacheEntry *RCE; Status = RouteToDestination(Addr, ScopeId, NULL, 0, &RCE); if (Status != IP_SUCCESS) { return Status; } Ire->ire_Length = sizeof(IP6RouteEntry); Ire->ire_Source = RCE->NTE->Address; Ire->ire_ScopeId = DetermineScopeId(&RCE->NTE->Address, RCE->NTE->IF); Ire->ire_IfIndex = RCE->NTE->IF->Index; ReleaseRCE(RCE); return Status; }