windows-nt/Source/XPSP1/NT/net/tcpip/tpipv6/tcpip6/ip6/select.c
2020-09-26 16:20:57 +08:00

831 lines
23 KiB
C

// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 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:
//
// Source address selection and destination address ordering.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "route.h"
#include "select.h"
KSPIN_LOCK SelectLock;
PrefixPolicyEntry *PrefixPolicyTable;
PrefixPolicyEntry PrefixPolicyNull;
//* InitSelect
//
// Initialize the address selection module.
//
void
InitSelect(void)
{
IPv6Addr Prefix;
KeInitializeSpinLock(&SelectLock);
//
// The default prefix policy, when nothing in the table matches.
// (Normally there will be a ::/0 policy.)
//
PrefixPolicyNull.Precedence = (uint) -1;
PrefixPolicyNull.SrcLabel = (uint) -1;
PrefixPolicyNull.DstLabel = (uint) -1;
//
// Configure persistent policies from the registry.
//
ConfigurePrefixPolicies();
}
//* UnloadSelect
//
// Called when the IPv6 stack is unloading.
//
void
UnloadSelect(void)
{
PrefixPolicyReset();
}
//* PrefixPolicyReset
//
// Deletes all prefix policies.
// Called with no locks held.
//
void
PrefixPolicyReset(void)
{
PrefixPolicyEntry *List;
PrefixPolicyEntry *PPE;
KIRQL OldIrql;
//
// Free the prefix policies.
//
KeAcquireSpinLock(&SelectLock, &OldIrql);
List = PrefixPolicyTable;
PrefixPolicyTable = NULL;
KeReleaseSpinLock(&SelectLock, OldIrql);
while ((PPE = List) != NULL) {
List = PPE->Next;
ExFreePool(PPE);
}
}
//* PrefixPolicyUpdate
//
// Updates the prefix policy table by creating a new policy entry
// or updating an existing entry.
//
void
PrefixPolicyUpdate(
const IPv6Addr *PolicyPrefix,
uint PrefixLength,
uint Precedence,
uint SrcLabel,
uint DstLabel)
{
IPv6Addr Prefix;
PrefixPolicyEntry *PPE;
KIRQL OldIrql;
ASSERT((Precedence != (uint)-1) &&
(SrcLabel != (uint)-1) &&
(DstLabel != (uint)-1));
//
// Ensure that the unused prefix bits are zero.
// This makes the prefix comparisons below safe.
//
CopyPrefix(&Prefix, PolicyPrefix, PrefixLength);
KeAcquireSpinLock(&SelectLock, &OldIrql);
for (PPE = PrefixPolicyTable; ; PPE = PPE->Next) {
if (PPE == NULL) {
//
// The prefix policy does not exist, so create it.
//
PPE = ExAllocatePool(NonPagedPool, sizeof *PPE);
if (PPE == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"PrefixPolicyUpdate: out of pool\n"));
break;
}
PPE->Prefix = Prefix;
PPE->PrefixLength = PrefixLength;
PPE->Precedence = Precedence;
PPE->SrcLabel = SrcLabel;
PPE->DstLabel = DstLabel;
PPE->Next = PrefixPolicyTable;
PrefixPolicyTable = PPE;
break;
}
if ((PPE->PrefixLength == PrefixLength) &&
IP6_ADDR_EQUAL(&PPE->Prefix, &Prefix)) {
//
// Update the existing policy.
//
PPE->Precedence = Precedence;
PPE->SrcLabel = SrcLabel;
PPE->DstLabel = DstLabel;
break;
}
}
KeReleaseSpinLock(&SelectLock, OldIrql);
}
//* PrefixPolicyDelete
//
// Updates the prefix policy table by deleting a policy entry.
//
void
PrefixPolicyDelete(
const IPv6Addr *PolicyPrefix,
uint PrefixLength)
{
IPv6Addr Prefix;
PrefixPolicyEntry **PrevPPE;
PrefixPolicyEntry *PPE;
KIRQL OldIrql;
//
// Ensure that the unused prefix bits are zero.
// This makes the prefix comparisons below safe.
//
CopyPrefix(&Prefix, PolicyPrefix, PrefixLength);
KeAcquireSpinLock(&SelectLock, &OldIrql);
for (PrevPPE = &PrefixPolicyTable; ; PrevPPE = &PPE->Next) {
PPE = *PrevPPE;
if (PPE == NULL) {
//
// The prefix policy does not exist, so do nothing.
//
break;
}
if ((PPE->PrefixLength == PrefixLength) &&
IP6_ADDR_EQUAL(&PPE->Prefix, &Prefix)) {
//
// Delete the prefix policy.
//
*PrevPPE = PPE->Next;
ExFreePool(PPE);
break;
}
}
KeReleaseSpinLock(&SelectLock, OldIrql);
}
void
PrefixPolicyLookup(
const IPv6Addr *Addr,
uint *Precedence,
uint *SrcLabel,
uint *DstLabel)
{
PrefixPolicyEntry *PPE, *BestPPE = NULL;
KIRQL OldIrql;
KeAcquireSpinLock(&SelectLock, &OldIrql);
for (PPE = PrefixPolicyTable; PPE != NULL; PPE = PPE->Next) {
if (HasPrefix(Addr, &PPE->Prefix, PPE->PrefixLength)) {
if ((BestPPE == NULL) ||
(BestPPE->PrefixLength < PPE->PrefixLength)) {
//
// So far this is our best match.
//
BestPPE = PPE;
}
}
}
if (BestPPE == NULL) {
//
// There were no matches, so return default values.
//
BestPPE = &PrefixPolicyNull;
}
//
// Return information from the best matching policy.
//
if (Precedence != NULL)
*Precedence = BestPPE->Precedence;
if (SrcLabel != NULL)
*SrcLabel = BestPPE->SrcLabel;
if (DstLabel != NULL)
*DstLabel = BestPPE->DstLabel;
KeReleaseSpinLock(&SelectLock, OldIrql);
}
//* FindBestSourceAddress
//
// Given an outgoing interface and a destination address,
// finds the best source address (NTE) to use.
//
// May be called with the route cache lock held.
//
// If found, returns a reference for the NTE.
//
NetTableEntry *
FindBestSourceAddress(
Interface *IF, // Interface we're sending from.
const IPv6Addr *Dest) // Destination we're sending to.
{
NetTableEntry *BestNTE = NULL;
ushort DestScope;
uint Length, BestLength;
uint DstLabel, SrcLabel, BestSrcLabel;
AddressEntry *ADE;
NetTableEntry *NTE;
KIRQL OldIrql;
DestScope = AddressScope(Dest);
PrefixPolicyLookup(Dest, NULL, NULL, &DstLabel);
KeAcquireSpinLock(&IF->Lock, &OldIrql);
for (ADE = IF->ADE; ADE != NULL; ADE = ADE->Next) {
NTE = (NetTableEntry *)ADE;
//
// Only consider valid (preferred & deprecated) unicast addresses.
//
if ((NTE->Type == ADE_UNICAST) && IsValidNTE(NTE)) {
Length = CommonPrefixLength(Dest, &NTE->Address);
if (Length == IPV6_ADDRESS_LENGTH) {
//
// Rule 1: Prefer same address.
// No need to keep looking.
//
BestNTE = NTE;
break;
}
PrefixPolicyLookup(&NTE->Address, NULL, &SrcLabel, NULL);
if (BestNTE == NULL) {
//
// We don't have a choice yet, so take what we can get.
//
FoundAddress:
BestNTE = NTE;
BestSrcLabel = SrcLabel;
BestLength = Length;
}
else if (BestNTE->Scope != NTE->Scope) {
//
// Rule 2: Prefer appropriate scope.
// If one is bigger & one smaller than the destination,
// we should use the address that is bigger.
// If both are bigger than the destination,
// we should use the address with smaller scope.
// If both are smaller than the destination,
// we should use the address with larger scope.
//
if (BestNTE->Scope < NTE->Scope) {
if (BestNTE->Scope < DestScope)
goto FoundAddress;
}
else {
if (DestScope <= NTE->Scope)
goto FoundAddress;
}
}
else if (BestNTE->DADState != NTE->DADState) {
//
// Rule 3: Avoid deprecated addresses.
//
if (BestNTE->DADState < NTE->DADState)
goto FoundAddress;
}
//
// Rule 4: Prefer home addresses.
// Not yet implemented, pending mobility support.
//
// Rule 5: Prefer outgoing interface.
// Not needed, because we only consider addresses
// assigned to the outgoing interface.
//
else if ((BestSrcLabel == DstLabel) != (SrcLabel == DstLabel)) {
//
// Rule 6: Prefer matching label.
// One source address has a label matching
// the destination, and the other doesn't.
// Choose the one with the matching label.
//
if (SrcLabel == DstLabel)
goto FoundAddress;
}
else if ((BestNTE->AddrConf == ADDR_CONF_ANONYMOUS) !=
(NTE->AddrConf == ADDR_CONF_ANONYMOUS)) {
//
// Rule 7: Prefer anonymous addresses.
//
if (NTE->AddrConf == ADDR_CONF_ANONYMOUS)
goto FoundAddress;
}
else {
//
// Rule 8: Use longest matching prefix.
//
if (BestLength < Length)
goto FoundAddress;
}
}
}
if (BestNTE != NULL)
AddRefNTE(BestNTE);
KeReleaseSpinLock(&IF->Lock, OldIrql);
return BestNTE;
}
//* ProcessSiteLocalAddresses
//
// Examines the input array of addresses
// and either removes unqualified site-local addresses
// or qualifies them with the appropriate site scope-id,
// depending on whether there are any global addresses
// in the array that match in the site prefix table.
//
// Rearranges the key array, not the input address array.
// Modifies the scope-ids of site-local addresses in the array.
//
void
ProcessSiteLocalAddresses(
TDI_ADDRESS_IP6 *Addrs,
uint *Key,
uint *pNumAddrs)
{
uint NumAddrs = *pNumAddrs;
int SawSiteLocal = FALSE;
int SawGlobal = FALSE;
uint i;
//
// First see if there are unqualified site-local addresses
// and global addresses in the array.
//
for (i = 0; i < NumAddrs; i++) {
TDI_ADDRESS_IP6 *Tdi = &Addrs[Key[i]];
IPv6Addr *Addr = (IPv6Addr *) &Tdi->sin6_addr;
if (IsGlobal(Addr))
SawGlobal = TRUE;
else if (IsSiteLocal(Addr)) {
if (Tdi->sin6_scope_id == 0)
SawSiteLocal = TRUE;
}
}
if (SawSiteLocal && SawGlobal) {
uint ScopeId = 0;
//
// Check the global addresses against the site-prefix table,
// to determine the appropriate site scope-id.
// If we don't find a matching global address,
// we remove the site-local addresses.
// If we do find matching global addresses
// (all with the same site scope-id),
// then we update the site-local addresses' scope-id.
//
for (i = 0; i < NumAddrs; i++) {
TDI_ADDRESS_IP6 *Tdi = &Addrs[Key[i]];
IPv6Addr *Addr = (IPv6Addr *) &Tdi->sin6_addr;
if (IsGlobal(Addr)) {
uint ThisScopeId;
ThisScopeId = SitePrefixMatch(Addr);
if (ThisScopeId != 0) {
//
// This global address matches a site prefix.
//
if (ScopeId == 0) {
//
// Save the scope-id, but keep looking.
//
ScopeId = ThisScopeId;
}
else if (ScopeId != ThisScopeId) {
//
// We have found an inconsistency, so remove
// all unqualified site-local addresses.
//
ScopeId = 0;
break;
}
}
}
}
if (ScopeId == 0) {
uint j = 0;
//
// Remove all unqualified site-local addresses.
//
for (i = 0; i < NumAddrs; i++) {
TDI_ADDRESS_IP6 *Tdi = &Addrs[Key[i]];
IPv6Addr *Addr = (IPv6Addr *) &Tdi->sin6_addr;
if (IsSiteLocal(Addr) &&
(Tdi->sin6_scope_id == 0)) {
//
// Exclude this address from the key array.
//
;
}
else {
//
// Include this address in the key array.
//
Key[j++] = Key[i];
}
}
*pNumAddrs = j;
}
else {
//
// Set the scope-id of unqualified site-local addresses.
//
for (i = 0; i < NumAddrs; i++) {
TDI_ADDRESS_IP6 *Tdi = &Addrs[Key[i]];
IPv6Addr *Addr = (IPv6Addr *) &Tdi->sin6_addr;
if (IsSiteLocal(Addr) &&
(Tdi->sin6_scope_id == 0))
Tdi->sin6_scope_id = ScopeId;
}
}
}
}
//
// Records some information about a destination address:
// Its precedence, whether the preferred source address
// for the destination "matches" the destination,
// and if it does match, the common prefix length
// of the two addresses.
//
typedef struct SortAddrInfo {
uint Preference;
uint Precedence; // -1 indicates no precedence.
ushort Scope;
uchar Flags;
uchar CommonPrefixLen; // Valid if not SAI_FLAG_DONTUSE.
} SortAddrInfo;
#define SAI_FLAG_DONTUSE 0x1
#define SAI_FLAG_SCOPE_MISMATCH 0x2
#define SAI_FLAG_DEPRECATED 0x4
#define SAI_FLAG_LABEL_MISMATCH 0x8
//* CompareSortAddrInfo
//
// Compares two addresses A & B and returns
// an indication of their relative desirability
// as destination addresses:
// >0 means A is preferred,
// 0 means no preference,
// <0 means B is preferred.
//
// Instead of looking directly at the addresses,
// we look at some precomputed information.
//
int
CompareSortAddrInfo(SortAddrInfo *A, SortAddrInfo *B)
{
//
// Rule 1: Avoid unusable destinations.
//
if (A->Flags & SAI_FLAG_DONTUSE) {
if (B->Flags & SAI_FLAG_DONTUSE)
return 0; // No preference.
else
return -1; // Prefer B.
}
else {
if (B->Flags & SAI_FLAG_DONTUSE)
return 1; // Prefer A.
else
; // Fall through to code below.
}
if ((A->Flags & SAI_FLAG_SCOPE_MISMATCH) !=
(B->Flags & SAI_FLAG_SCOPE_MISMATCH)) {
//
// Rule 2: Prefer matching scope.
//
if (A->Flags & SAI_FLAG_SCOPE_MISMATCH)
return -1; // Prefer B.
else
return 1; // Prefer A.
}
if ((A->Flags & SAI_FLAG_DEPRECATED) !=
(B->Flags & SAI_FLAG_DEPRECATED)) {
//
// Rule 3: Avoid deprecated addresses.
//
if (A->Flags & SAI_FLAG_DEPRECATED)
return -1; // Prefer B.
else
return 1; // Prefer A.
}
//
// Rule 4: Prefer home addresses.
// Not yet implemented, pending mobility support.
//
if ((A->Flags & SAI_FLAG_LABEL_MISMATCH) !=
(B->Flags & SAI_FLAG_LABEL_MISMATCH)) {
//
// Rule 5: Prefer matching label.
//
if (A->Flags & SAI_FLAG_LABEL_MISMATCH)
return -1; // Prefer B.
else
return 1; // Prefer A.
}
if ((A->Precedence != (uint)-1) &&
(B->Precedence != (uint)-1) &&
(A->Precedence != B->Precedence)) {
//
// Rule 6: Prefer higher precedence.
//
if (A->Precedence > B->Precedence)
return 1; // Prefer A.
else
return -1; // Prefer B.
}
if (A->Preference != B->Preference) {
//
// Rule 7: Prefer *lower* preference.
// For example, this is used to prefer destinations reached via
// physical (native) interfaces over virtual (tunnel) interfaces.
//
if (A->Preference < B->Preference)
return 1; // Prefer A.
else
return -1; // Prefer B.
}
if (A->Scope != B->Scope) {
//
// Rule 8: Prefer smaller scope.
//
if (A->Scope < B->Scope)
return 1; // Prefer A.
else
return -1; // Prefer B.
}
if (A->CommonPrefixLen != B->CommonPrefixLen) {
//
// Rule 9: Use longest matching prefix.
//
if (A->CommonPrefixLen > B->CommonPrefixLen)
return 1; // Prefer A.
else
return -1; // Prefer B.
}
//
// We have no preference.
//
return 0;
}
//* SortDestAddresses
//
// Sorts the input array of addresses,
// from most preferred destination to least preferred.
//
// The address array is read-only;
// the Key array of indices is sorted.
//
void
SortDestAddresses(
const TDI_ADDRESS_IP6 *Addrs,
uint *Key,
uint NumAddrs)
{
SortAddrInfo *Info;
uint i, j;
Info = ExAllocatePool(NonPagedPool, sizeof *Info * NumAddrs);
if (Info == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"SortDestAddresses: no pool\n"));
return;
}
//
// Calculate some information about each destination address.
// This will be the basis for our sort.
//
for (i = 0; i < NumAddrs; i++) {
SortAddrInfo *info = &Info[i];
const TDI_ADDRESS_IP6 *Tdi = &Addrs[Key[i]];
const IPv6Addr *Addr = (const IPv6Addr *) &Tdi->sin6_addr;
uint DstLabel, SrcLabel;
//
// Lookup the precedence of this destination address and
// the desired label for source addresses used
// with this destination.
//
PrefixPolicyLookup(Addr, &info->Precedence, NULL, &DstLabel);
if (IsV4Mapped(Addr)) {
IPAddr V4Dest = ExtractV4Address(Addr);
IPAddr V4Source;
info->Scope = V4AddressScope(V4Dest);
if (TunnelGetSourceAddress(V4Dest, &V4Source)) {
IPv6Addr Source;
//
// Create an IPv4-mapped address.
//
CreateV4Mapped(&Source, V4Source);
info->Flags = 0;
info->CommonPrefixLen = (uchar)
CommonPrefixLength(Addr, &Source);
if (V4AddressScope(V4Source) != info->Scope)
info->Flags |= SAI_FLAG_SCOPE_MISMATCH;
//
// Lookup the label of the preferred source address.
//
PrefixPolicyLookup(&Source, NULL, &SrcLabel, NULL);
//
// We do not know interface/route metrics
// for IPv4, so just use zero.
//
info->Preference = 0;
if ((DstLabel != (uint)-1) &&
(SrcLabel != (uint)-1) &&
(DstLabel != SrcLabel)) {
//
// The best source address for this destination
// does not match the destination.
//
info->Flags |= SAI_FLAG_LABEL_MISMATCH;
}
}
else
info->Flags = SAI_FLAG_DONTUSE;
}
else {
RouteCacheEntry *RCE;
info->Scope = AddressScope(Addr);
//
// Find the preferred source address for this destination.
//
if (RouteToDestination(Addr, Tdi->sin6_scope_id,
NULL, 0, &RCE) == IP_SUCCESS) {
const IPv6Addr *Source = &RCE->NTE->Address;
Interface *IF = RCE->NCE->IF;
info->Flags = 0;
info->CommonPrefixLen = (uchar)
CommonPrefixLength(Addr, Source);
if (RCE->NTE->Scope != info->Scope)
info->Flags |= SAI_FLAG_SCOPE_MISMATCH;
if (RCE->NTE->DADState != DAD_STATE_PREFERRED)
info->Flags |= SAI_FLAG_DEPRECATED;
//
// Lookup the label of the preferred source address.
//
PrefixPolicyLookup(Source, NULL, &SrcLabel, NULL);
//
// REVIEW - Instead of using interface preference,
// would it be better to cache interface+route preference
// in the RCE?
//
info->Preference = IF->Preference;
//
// If the next-hop is definitely unreachable,
// then we don't want to use this destination.
// NB: No locking here, this is a heuristic check.
//
if ((IF->Flags & IF_FLAG_MEDIA_DISCONNECTED) ||
RCE->NCE->IsUnreachable)
info->Flags |= SAI_FLAG_DONTUSE;
ReleaseRCE(RCE);
if ((DstLabel != (uint)-1) &&
(SrcLabel != (uint)-1) &&
(DstLabel != SrcLabel)) {
//
// The best source address for this destination
// does not match the destination.
//
info->Flags |= SAI_FLAG_LABEL_MISMATCH;
}
}
else
info->Flags = SAI_FLAG_DONTUSE;
}
}
//
// Perform the actual sort operation.
// Because we expect NumAddrs to be small,
// we use a simple quadratic sort.
//
ASSERT(NumAddrs > 0);
for (i = 0; i < NumAddrs - 1; i++) {
for (j = i + 1; j < NumAddrs; j++) {
int Compare;
//
// As a tie-breaker, if the comparison function
// has no preference we look at the original
// position of the two addresses and prefer
// the one that came first.
//
Compare = CompareSortAddrInfo(&Info[i], &Info[j]);
if ((Compare < 0) ||
((Compare == 0) && (Key[j] < Key[i]))) {
uint TempKey;
SortAddrInfo TempInfo;
//
// Address j is preferred over address i,
// so swap addresses i & j to put j first.
//
TempKey = Key[i];
Key[i] = Key[j];
Key[j] = TempKey;
//
// We also have to swap the address info.
//
TempInfo = Info[i];
Info[i] = Info[j];
Info[j] = TempInfo;
}
}
}
ExFreePool(Info);
}