windows-nt/Source/XPSP1/NT/ds/dns/resolver/server/ncache.c
2020-09-26 16:20:57 +08:00

3063 lines
62 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 2000-2001 Microsoft Corporation
Module Name:
ncache.c
Abstract:
DNS Resolver Service
Cache routines
Author:
Jim Gilroy (jamesg) April 2001
Revision History:
--*/
#include "local.h"
//
// Heap corruption tracking
//
#define HEAPPROB 1
#define BAD_PTR (PVOID)(-1)
//
// Cache entry definitions
//
// Starting cache record count
//
#define CACHE_DEFAULT_SET_COUNT 3
#if 0
// Should be private but is exposed in remote
// cache enum routines.
typedef struct _CacheEntry
{
struct _CacheEntry * pNext;
PWSTR pName;
DWORD Reserved;
DWORD MaxCount;
PDNS_RECORD Records[ 1 ];
}
CACHE_ENTRY, *PCACHE_ENTRY;
#endif
//
// Cache heap
//
HANDLE g_CacheHeap = NULL;
//
// Cache hash table
//
PCACHE_ENTRY * g_HashTable = NULL;
#define INITIAL_CACHE_HEAP_SIZE (16*1024)
//
// Runtime globals
//
DWORD g_CurrentCacheTime;
DWORD g_RecordSetCount;
DWORD g_RecordSetCountLimit;
DWORD g_RecordSetCountThreshold;
DWORD g_RecordSetCache;
DWORD g_RecordSetFree;
DWORD g_EntryCount;
DWORD g_EntryAlloc;
DWORD g_EntryFree;
BOOL g_fLoadingHostsFile;
//
// Garbage collection
//
BOOL g_GarbageCollectFlag = FALSE;
DWORD g_NextGarbageIndex = 0;
DWORD g_NextGarbageTime = 0;
#define GARBAGE_LOCKOUT_INTERVAL (600) // no more then every ten minutes
//
// Wakeup flag
//
BOOL g_WakeFlag = FALSE;
//
// Cache limits
// - min count of records to hold
// - size of band in which garbage collection occurs
//
#if DBG
#define MIN_DYNAMIC_RECORD_COUNT (20)
#define CLEANUP_RECORD_COUNT_BAND (5)
#else
#define MIN_DYNAMIC_RECORD_COUNT (50)
#define CLEANUP_RECORD_COUNT_BAND (30)
#endif
//
// Static records (hosts file)
//
#define IS_STATIC_RR(prr) (IS_HOSTS_FILE_RR(prr) || IS_CLUSTER_RR(prr))
//
// Compute a hash table index value for a string
//
#define EOS (L'\0')
#define COMPUTE_STRING_HASH_1( _String, _ulHashTableSize, _lpulHash ) \
{ \
PWCHAR p; \
ULOND h = 0, g; \
\
for ( p = _String; *p != EOS; p = p + 1 ) \
{ \
h = ( h << 4 ) + (DWORD) (*p); \
if ( g = h&0xf0000000 ) \
{ \
h = h ^ ( g >> 24 ); \
h = h ^ g; \
} \
} \
*_lpulHash = h % _ulHashTableSize; \
}
//
// Compute a hash table index value for a string
// which is invairant to case
//
#define COMPUTE_STRING_HASH_2( _String, _ulHashTableSize, _lpulHash ) \
{ \
PWCHAR _p = _String; \
PWCHAR _ep = _p + wcslen( _String ); \
ULONG h = 0; \
\
while( _p < _ep ) \
{ \
h <<= 1; \
h ^= *_p++; \
} \
\
*_lpulHash = h % _ulHashTableSize; \
}
//
// Private prototypes
//
BOOL
Cache_FlushEntryRecords(
IN OUT PCACHE_ENTRY pEntry,
IN DWORD Level,
IN WORD wType
);
VOID
Cache_FlushBucket(
IN ULONG Index,
IN WORD FlushLevel
);
//
// Cache Implementation
//
// Cache is implemented as a hash on name, with chaining in the individual
// buckets. Individual name entries are blocks with name pointer and array
// of up to 3 RR set pointers. The new names\entries are put at the front of
// the bucket chain, so the oldest are at the rear.
//
//
// Cleanup:
//
// The cleanup strategy is to time out all RR sets and cleanup everything
// possible as a result. Then entries beyond a max bucket size (a resizable
// global) are deleted, the oldest queries deleted first.
//
// Ideally, we'd like to keep the most useful entries in the cache while
// being able to limit the overall cache size.
//
// A few observations:
//
// 1) max bucket size is worthless; if sufficient for pruning, it would be
// too small to allow non-uniform distributions
//
// 2) LRU should be required; on busy cache shouldn't prune something queried
// "a while" ago that is being used all the time; that adds much more traffic
// than something recently queried but then unused;
//
// 3) if necessary an LRU index could be kept; but probably some time bucket
// counting to know how "deep" pruning must be is adequate
//
//
// Memory:
//
// Currently hash itself and hash entries come from private resolver heap.
// However, RR sets are built by record parsing of messages received in dnsapi.dll
// and hence are built by the default dnsapi.dll allocator. We must match it.
//
// The downside of this is twofold:
// 1) By being in process heap, we are exposed (debug wise) to any poor code
// in services.exe. Hopefully, there are getting better, but anything that
// trashes memory is likely to cause us to have to debug, because we are the highest
// use service.
// 2) Flush \ cleanup is easy. Just kill the heap.
//
// There are several choices:
//
// 0) Copy the records. We are still susceptible to memory corruption ... but the
// interval is shorter, since we don't keep anything in process heap.
//
// 1) Query can directly call dnslib.lib query routines. Since dnslib.lib is
// explicitly compiled in, it's global for holding the allocators is modules, rather
// than process specific.
//
// 2) Add some parameter to query routines that allows pass of allocator down to
// lowest level. At high level this is straightforward. At lower level it maybe
// problematic. There may be a way to do it with a flag where the allocator is
// "optional" and used only when a flag is set.
//
//
// Cache functions
//
VOID
Cache_Lock(
IN BOOL fNoStart
)
/*++
Routine Description:
Lock the cache
Arguments:
None.
Return Value:
None -- cache is locked.
--*/
{
DNSDBG( LOCK, ( "Enter Cache_Lock() ..." ));
EnterCriticalSection( &CacheCritSec );
DNSDBG( LOCK, (
"through lock (r=%d)\n",
CacheCritSec.RecursionCount ));
// update global time (for TTL set and timeout)
//
// this allows us to eliminate multiple time calls
// within cache
g_CurrentCacheTime = Dns_GetCurrentTimeInSeconds();
//
// if cache not loaded -- load
// this allows us to avoid load on every PnP until we
// are actually queried
//
if ( !fNoStart && !g_HashTable )
{
DNSDBG( ANY, (
"No hash table when took lock -- initializing!\n" ));
Cache_Initialize();
}
}
VOID
Cache_Unlock(
VOID
)
/*++
Routine Description:
Unlock the cache
Arguments:
None.
Return Value:
None.
--*/
{
DNSDBG( LOCK, (
"Cache_Unlock() r=%d\n",
CacheCritSec.RecursionCount ));
LeaveCriticalSection( &CacheCritSec );
}
DNS_STATUS
Cache_Initialize(
VOID
)
/*++
Routine Description:
Initialize the cache.
Create events and locks and setup basic hash.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful.
ErrorCode on failure.
--*/
{
DNS_STATUS status;
DWORD carryCount;
DNSDBG( INIT, ( "Cache_Initialize()\n" ));
//
// lock -- with "no-start" set to avoid recursion
//
LOCK_CACHE_NO_START();
//
// create cache heap
//
// want to have own heap
// 1) to simplify flush\shutdown
// 2) keep us from "entanglements" with poor services
//
g_CacheHeap = HeapCreate( 0, INITIAL_CACHE_HEAP_SIZE, 0 );
if ( !g_CacheHeap )
{
status = ERROR_NOT_ENOUGH_MEMORY;
g_HashTable = NULL;
goto Done;
}
g_HashTable = CACHE_HEAP_ALLOC_ZERO(
sizeof(PCACHE_ENTRY) * g_HashTableSize );
if ( !g_HashTable )
{
status = ERROR_NOT_ENOUGH_MEMORY;
HeapDestroy( g_CacheHeap );
g_CacheHeap = NULL;
goto Done;
}
g_WakeFlag = FALSE;
g_EntryCount = 0;
g_EntryAlloc = 0;
g_EntryFree = 0;
g_RecordSetCount = 0;
g_RecordSetCache = 0;
g_RecordSetFree = 0;
// eliminate cache size checks during hosts file load
g_RecordSetCountLimit = MAXDWORD;
g_RecordSetCountThreshold = MAXDWORD;
//
// load hosts file into cache
//
g_fLoadingHostsFile = TRUE;
InitCacheWithHostFile();
g_fLoadingHostsFile = FALSE;
//
// set cache size limit
// - above what loaded from hosts file
// - always allow some dynamic space regardless of
// g_MaxCacheSize
// - create slightly higher threshold value for kicking
// off cleanup so cleanup not running all the time
//
carryCount = g_MaxCacheSize;
if ( carryCount < MIN_DYNAMIC_RECORD_COUNT )
{
carryCount = MIN_DYNAMIC_RECORD_COUNT;
}
g_RecordSetCountLimit = g_RecordSetCount + carryCount;
g_RecordSetCountThreshold = g_RecordSetCountLimit + CLEANUP_RECORD_COUNT_BAND;
Done:
UNLOCK_CACHE();
return NO_ERROR;
}
DNS_STATUS
Cache_Shutdown(
VOID
)
/*++
Routine Description:
Shutdown the cache.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful.
ErrorCode on failure.
--*/
{
DNSDBG( INIT, ( "Cache_Shutdown()\n" ));
//
// clean out cache and delete cache heap
// - currently Cache_Flush() does just this
//
return Cache_Flush();
}
DNS_STATUS
Cache_Flush(
VOID
)
/*++
Routine Description:
Flush the cache.
This flushes all the cache data and rereads host file but does NOT
shut down and restart cache threads (host file monitor or multicast).
Arguments:
None
Return Value:
ERROR_SUCCESS if successful.
ErrorCode on rebuild failure.
--*/
{
DWORD status = ERROR_SUCCESS;
WORD ihash;
WORD RecordIter;
DNSDBG( ANY, ( "\nCache_Flush()\n" ));
//
// wake\stop garbage collection
//
g_WakeFlag = TRUE;
//
// lock with "no start" flag
// - avoids creating cache structs if they don't exist
//
LOCK_CACHE_NO_START();
DNSLOG_F1( "Flushing DNS Cache" );
DNSLOG_F3(
" Before Cache_Flush(): entries %d, record %d",
g_EntryCount,
g_RecordSetCount );
//
// clear entries in each hash bucket
//
if ( g_HashTable )
{
for ( ihash = 0;
ihash < g_HashTableSize;
ihash++ )
{
Cache_FlushBucket(
ihash,
FLUSH_LEVEL_CLEANUP );
}
}
DNSDBG( CACHE, (
"After flushing cache:\n"
"\trecord count = %d\n"
"\tentry count = %d\n",
g_RecordSetCount,
g_EntryCount ));
DNS_ASSERT( g_RecordSetCount == 0 );
DNS_ASSERT( g_EntryCount == 0 );
DNSLOG_F3(
" After Cache_Flush() flush: entries %d, record %d",
g_EntryCount,
g_RecordSetCount );
//
// Note: can NOT delete the cache without stopping mcast
// thread which currently uses cache heap
//
// DCR: have all data in cache in single heap
// - protected
// - single destroy cleans up
// once cleaned up, delete heap
if ( g_CacheHeap )
{
HeapDestroy( g_CacheHeap );
g_CacheHeap = NULL;
}
g_HashTable = NULL;
//
// dump local IP list
// - not dumping on shutdown as the IP cleanup happens
// first and takes away the CS;
//
// note to reviewer:
// this is equivalent to the previous behavior where
// Cache_Flush() FALSE was shutdown and
// everything else used TRUE (for restart) which did a
// RefreshLocalAddrArray() to rebuild IP list
// now we simply dump the IP list rather than rebuilding
//
if ( !g_StopFlag )
{
ClearLocalAddrArray();
}
DNSDBG( ANY, ( "Leave Cache_Flush()\n\n" ));
UNLOCK_CACHE();
return( status );
}
//
// Cache utilities
//
BOOL
Cache_IsRecordTtlValid(
IN PDNS_RECORD pRecord
)
/*++
Routine Description:
Check if TTL is still valid (or has timed out).
Arguments:
pRecord -- record to check
Return Value:
TRUE -- if TTL is still valid
FALSE -- if TTL has timed out
--*/
{
//
// static or TTL not timed out => valid
//
// note: currently flushing all records on PnP, but this is
// not strickly necessary; if stop this then MUST change
// this to whack negative cache entries that are older
// than last PnP time
//
if ( IS_STATIC_RR(pRecord) )
{
return( TRUE );
}
else
{
return( (LONG)(pRecord->dwTtl - g_CurrentCacheTime) > 0 );
}
}
//
// Cache entry routines
//
DWORD
getHashIndex(
IN PWSTR pName,
IN DWORD NameLength OPTIONAL
)
/*++
Routine Description:
Create cannonical cache form of name.
Note: no test for adequacy of buffer is done.
Arguments:
pName -- name
NameLength -- NameLength, OPTIONAL
Return Value:
None
--*/
{
register PWCHAR pstring;
register WCHAR wch;
register DWORD hash = 0;
//
// build hash by XORing characters
//
pstring = pName;
while ( wch = *pstring++ )
{
hash <<= 1;
hash ^= wch;
}
//
// mod over hash table size
//
return( hash % g_HashTableSize );
}
BOOL
makeCannonicalCacheName(
OUT PWCHAR pNameBuffer,
IN DWORD BufferLength,
IN PWSTR pName,
IN DWORD NameLength OPTIONAL
)
/*++
Routine Description:
Create cannonical cache form of name.
Arguments:
pNameBuffer -- buffer to hold cache name
BufferLength -- length of buffer
pName -- ptr to name string
NameLength -- optional, saves wsclen() call if known
Return Value:
TRUE if successful.
FALSE on bogus name.
--*/
{
INT count;
DNSDBG( TRACE, (
"makeCannonicalCacheName( %S )\n",
pName ));
//
// get length if not specified
//
if ( NameLength == 0 )
{
NameLength = wcslen( pName );
}
//
// copy and downcase string
// - "empty" buffer for prefix happiness
//
*pNameBuffer = (WCHAR) 0;
count = Dns_MakeCanonicalNameW(
pNameBuffer,
BufferLength,
pName,
NameLength+1 // convert null terminator
);
if ( count == 0 )
{
ASSERT( GetLastError() == ERROR_INSUFFICIENT_BUFFER );
return( FALSE );
}
ASSERT( count == (INT)NameLength+1 );
//
// whack any trailing dot
// - except for root node
//
count--; // account for null terminator
DNS_ASSERT( count == NameLength );
if ( count > 1 &&
pNameBuffer[count - 1] == L'.' )
{
pNameBuffer[count - 1] = 0;
}
return( TRUE );
}
PCACHE_ENTRY
Cache_CreateEntry(
IN PWSTR pName,
IN BOOL fCanonical
)
/*++
Routine Description:
Create cache entry, including allocation.
Arguments:
pName -- name
fCanonical -- TRUE if name already in cannonical form
Return Value:
Ptr to newly allocated cache entry.
NULL on error.
--*/
{
ULONG index = 0;
PCACHE_ENTRY pentry = NULL;
DWORD nameLength;
DWORD fixedLength;
PWCHAR pnameCache = NULL;
DNSDBG( TRACE, (
"Cache_CreateEntry( %S )\n",
pName ));
if ( !pName || !g_HashTable )
{
return NULL;
}
//
// alloc
//
nameLength = wcslen( pName );
fixedLength = sizeof(CACHE_ENTRY) +
(sizeof(PDNS_RECORD) * (CACHE_DEFAULT_SET_COUNT-1));
pentry = (PCACHE_ENTRY) CACHE_HEAP_ALLOC_ZERO(
fixedLength +
sizeof(WCHAR) * (nameLength+1) );
if ( !pentry )
{
goto Fail;
}
pentry->MaxCount = CACHE_DEFAULT_SET_COUNT;
pnameCache = (PWSTR) ((PBYTE)pentry + fixedLength);
//
// build the name
//
if ( fCanonical )
{
wcscpy( pnameCache, pName );
}
else
{
if ( !makeCannonicalCacheName(
pnameCache,
nameLength+1,
pName,
nameLength ) )
{
goto Fail;
}
}
pentry->pName = pnameCache;
//
// insert cache entry into cache -- first entry in bucket
//
index = getHashIndex( pnameCache, nameLength );
pentry->pNext = g_HashTable[ index ];
g_HashTable[ index ] = pentry;
g_EntryCount++;
g_EntryAlloc++;
//
// DCR: need overload detection
//
return pentry;
Fail:
// dump entry
if ( pentry )
{
CACHE_HEAP_FREE( pentry );
}
return NULL;
}
VOID
Cache_FreeEntry(
IN OUT PCACHE_ENTRY pEntry
)
/*++
Routine Description:
Free cache entry.
Arguments:
pEntry -- cache entry to free
Globals:
g_EntryCount -- decremented appropriately
g_NumberOfRecordsInCache -- decremented appropriately
Return Value:
None
--*/
{
INT iter;
DNSDBG( TRACE, (
"Cache_FreeEntry( %p )\n",
pEntry ));
//
// free entry
// - records
// - name
// - entry itself
//
if ( pEntry )
{
Cache_FlushEntryRecords(
pEntry,
FLUSH_LEVEL_CLEANUP,
0 );
#if 0
if ( pEntry->pNext )
{
DNSLOG_F1( "Cache_FreeEntry is deleting an entry that still points to other entries!" );
}
#endif
#if HEAPPROB
pEntry->pNext = DNS_BAD_PTR;
#endif
CACHE_HEAP_FREE( pEntry );
g_EntryFree--;
g_EntryCount--;
}
}
PCACHE_ENTRY
Cache_FindEntry(
IN PWSTR pName,
IN BOOL fCreate
)
/*++
Routine Description:
Find or create entry for name in cache.
Arguments:
pName -- name to find
fCreate -- TRUE to create if not found
Return Value:
Ptr to cache entry -- if successful.
NULL on failure.
--*/
{
ULONG index;
PCACHE_ENTRY pentry;
PCACHE_ENTRY pprevEntry = NULL;
WCHAR hashName[ DNS_MAX_NAME_BUFFER_LENGTH+4 ];
if ( !g_HashTable )
{
return NULL;
}
if ( !pName )
{
DNS_ASSERT( FALSE );
return NULL;
}
DNSDBG( TRACE, (
"Cache_FindEntry( %S, create=%d )\n",
pName,
fCreate ));
//
// build cache name
// - if invalid (too long) bail
//
if ( !makeCannonicalCacheName(
hashName,
DNS_MAX_NAME_BUFFER_LENGTH,
pName,
0 ) )
{
return NULL;
}
//
// find entry in cache
//
LOCK_CACHE();
index = getHashIndex( hashName, 0 );
pentry = g_HashTable[ index ];
DNSDBG( OFF, (
"in Cache_FindEntry\n"
"\tname = %S\n"
"\tindex = %d\n"
"\tpentry = %p\n",
hashName,
index,
pentry ));
while( pentry )
{
if ( DnsNameCompare_W( hashName, pentry->pName ) )
{
//
// found entry
// - move to front, if not already there
if ( pprevEntry )
{
pprevEntry->pNext = pentry->pNext;
pentry->pNext = g_HashTable[ index ];
g_HashTable[ index ] = pentry;
}
break;
}
ELSE
{
DNSDBG( OFF, (
"in Cache_FindEntry -- failed name compare\n"
"\tout name = %S\n"
"\tpentry = %p\n"
"\tname = %S\n",
hashName,
pentry,
pentry->pName ));
}
pprevEntry = pentry;
pentry = pentry->pNext;
}
//
// if not found -- create?
//
// DCR: optimize for create
//
if ( !pentry && fCreate )
{
pentry = Cache_CreateEntry(
hashName,
TRUE // name already canonical
);
}
DNS_ASSERT( !pentry || g_HashTable[ index ] == pentry );
UNLOCK_CACHE();
DNSDBG( TRACE, (
"Leave Cache_FindEntry\n"
"\tname = %S\n"
"\tindex = %d\n"
"\tpentry = %p\n",
hashName,
index,
pentry ));
return pentry;
}
PDNS_RECORD
Cache_FindEntryRecords(
IN PCACHE_ENTRY pEntry,
IN WORD wType
)
/*++
Routine Description:
Find entry in cache.
Arguments:
pEntry -- cache entry to check
Type -- record type to find
Return Value:
Ptr to record set of desired type -- if found.
NULL if not found.
--*/
{
WORD iter;
PDNS_RECORD prr;
DNSDBG( TRACE, (
"Cache_FindEntryRecords %p, type=%d )\n",
pEntry,
wType ));
//
// check all the records at the cache entry
//
for ( iter = 0;
iter < pEntry->MaxCount;
iter++ )
{
prr = pEntry->Records[iter];
if ( !prr )
{
continue;
}
if ( !Cache_IsRecordTtlValid( prr ) )
{
DNSDBG( TRACE, (
"Whacking timed out record %p at cache entry %p\n",
prr,
pEntry ));
Dns_RecordListFree( prr );
pEntry->Records[iter] = NULL;
g_RecordSetCount--;
g_RecordSetFree--;
continue;
}
//
// find matching type
// - direct type match
// - NAME_ERROR
//
if ( prr->wType == wType ||
( prr->wType == DNS_TYPE_ANY &&
prr->wDataLength == 0 ) )
{
goto Done;
}
//
// CNAME match
// - walk list and determine if for matching type
if ( prr->wType == DNS_TYPE_CNAME &&
wType != DNS_TYPE_CNAME )
{
PDNS_RECORD prrChain = prr->pNext;
while ( prrChain )
{
if ( prrChain->wType == wType )
{
// chain to desired type -- take RR set
goto Done;
}
prrChain = prrChain->pNext;
}
}
// records for another type -- continue
}
// type not found
prr = NULL;
Done:
DNSDBG( TRACE, (
"Leave Cache_FindEntryRecords) => %p\n",
prr ));
return prr;
}
BOOL
Cache_FlushEntryRecords(
IN OUT PCACHE_ENTRY pEntry,
IN DWORD Level,
IN WORD wType
)
/*++
Routine Description:
Free cache entry.
Arguments:
pEntry -- cache entry to flush
FlushLevel -- flush level
FLUSH_LEVEL_NORMAL -- flush matching type, invalid, NAME_ERROR
FLUSH_LEVEL_WIRE -- to flush all wire data, but leave hosts and cluster
FLUSH_LEVEL_INVALID -- flush only invalid records
FLUSH_LEVEL_STRONG -- to flush all but hosts file
FLUSH_LEVEL_CLEANUP -- to flush all records for full cache flush
wType -- flush type for levels with type
DNS type -- to flush specifically this type
Globals:
g_EntryCount -- decremented appropriately
g_NumberOfRecordsInCache -- decremented appropriately
Return Value:
TRUE if entry flushed completely.
FALSE if records left.
--*/
{
INT iter;
BOOL recordsLeft = FALSE;
DNSDBG( TRACE, (
"Cache_FlushEntryRecords( %p, %08x, %d )\n",
pEntry,
Level,
wType ));
//
// loop through records sets -- flush where appropriate
//
// CLEANUP flush
// - everything
//
// STRONG (user initiated) flush
// - all cached records, including cluster
// but hostsfile saved
//
// WIRE flush
// - all wire cached records
// hosts file AND cluster saved
//
// INVALID flush
// - timedout only
//
// NORMAL flush (regular flush done on caching)
// - timed out records
// - records of desired type
// - NAME_ERROR
//
for ( iter = 0;
iter < (INT)pEntry->MaxCount;
iter++ )
{
PDNS_RECORD prr = pEntry->Records[iter];
BOOL flush;
if ( !prr )
{
continue;
}
//
// switch on flush type
// yes there are optimizations, but this is simple
//
if ( Level == FLUSH_LEVEL_NORMAL )
{
flush = ( !IS_STATIC_RR(prr)
&&
( prr->wType == wType ||
( prr->wType == DNS_TYPE_ANY &&
prr->wDataLength == 0 ) ) );
}
else if ( Level == FLUSH_LEVEL_WIRE )
{
flush = !IS_STATIC_RR(prr);
}
else if ( Level == FLUSH_LEVEL_INVALID )
{
flush = !Cache_IsRecordTtlValid(prr);
}
else if ( Level == FLUSH_LEVEL_CLEANUP )
{
flush = TRUE;
}
else
{
DNS_ASSERT( Level == FLUSH_LEVEL_STRONG );
flush = !IS_HOSTS_FILE_RR(prr);
}
if ( flush )
{
pEntry->Records[iter] = NULL;
Dns_RecordListFree( prr );
g_RecordSetCount--;
g_RecordSetFree--;
}
else
{
recordsLeft = TRUE;
}
}
return !recordsLeft;
}
VOID
Cache_FlushBucket(
IN ULONG Index,
IN WORD FlushLevel
)
/*++
Routine Description:
Cleanup cache bucket.
Arguments:
Index -- Index of hash bucket to trim.
FlushLevel -- level of flush desired
see Cache_FlushEntryRecords() for description of
flush levels
Return Value:
None
--*/
{
PCACHE_ENTRY pentry;
PCACHE_ENTRY pprev;
INT countCompleted;
DNSDBG( CACHE, (
"Cache_FlushBucket( %d, %08x )\n",
Index,
FlushLevel ));
//
// flush entries in this bucket
//
// note: using hack here that hash table pointer can
// be treated as cache entry for purposes of accessing
// it's next pointer (since it's the first field in
// a CACHE_ENTRY)
// if this changes, must explicitly fix up "first entry"
// case or move to double-linked list that can free
// empty penty without regard to it's location
//
if ( !g_HashTable )
{
return;
}
//
// flush entries
//
// avoid holding lock too long by handling no more then
// fifty entries at a time
// note: generally 50 entries will cover entire bucket but
// can still be completed in reasonable time;
//
// DCR: smarter flush -- avoid lock\unlock
// peer into CS and don't unlock when no one waiting
// if waiting unlock and give up timeslice
// DCR: some LRU flush for garbage collection
//
countCompleted = 0;
while ( 1 )
{
INT count = 0;
INT countStop = countCompleted + 50;
LOCK_CACHE_NO_START();
if ( !g_HashTable )
{
UNLOCK_CACHE();
break;
}
DNSDBG( CACHE, (
"locked for bucket flush -- completed=%d, stop=%d\n",
count,
countStop ));
pprev = (PCACHE_ENTRY) &g_HashTable[ Index ];
while ( pentry = pprev->pNext )
{
// bypass any previously checked entries
if ( count++ < countCompleted )
{
pprev = pentry;
continue;
}
if ( count > countStop )
{
break;
}
// flush -- if successful cut from list and
// drop counts so countCompleted used in bypass
// will be correct and won't skip anyone
if ( Cache_FlushEntryRecords(
pentry,
FlushLevel,
0 ) )
{
pprev->pNext = pentry->pNext;
Cache_FreeEntry( pentry );
count--;
countStop--;
continue;
}
pprev = pentry;
}
UNLOCK_CACHE();
countCompleted = count;
// stop when
// - cleared all the entries in the bucket
// - shutdown, except exempt the shutdown flush itself
if ( !pentry ||
(g_StopFlag && FlushLevel != FLUSH_LEVEL_CLEANUP) )
{
break;
}
}
DNSDBG( CACHE, (
"Leave Cache_FlushBucket( %d, %08x )\n"
"\trecord count = %d\n"
"\tentry count = %d\n",
Index,
FlushLevel,
g_RecordSetCount,
g_EntryCount ));
}
//
// Cache interface routines
//
VOID
Cache_PrepareRecordList(
IN OUT PDNS_RECORD pRecordList
)
/*++
Routine Description:
Prepare record list for cache.
Arguments:
pRecordList - record list to put in cache
Return Value:
Ptr to screened, prepared record list.
--*/
{
PDNS_RECORD prr = pRecordList;
PDNS_RECORD pnext;
DWORD ttl;
DWORD maxTtl;
DNSDBG( TRACE, (
"Cache_PrepareRecordList( rr=%p )\n",
prr ));
if ( !prr )
{
return;
}
//
// static (currently host file) TTL records
//
// currently no action required -- records come one
// at a time and no capability to even to the pName=NULL
// step
//
if ( IS_STATIC_RR(prr) )
{
return;
}
//
// wire records get relative TTL
// - compute minimum TTL for set
// - save TTL as timeout (offset by TTL from current time)
//
// DCR: TTL still not per set
// - but this is at least better than Win2K where
// multiple sets and did NOT find minimum
//
maxTtl = g_MaxCacheTtl;
if ( prr->wType == DNS_TYPE_SOA )
{
maxTtl = g_MaxSOACacheEntryTtlLimit;
}
//
// get caching TTL
// - minimum TTL in set
// - offset from current time
ttl = Dns_RecordListGetMinimumTtl( prr );
if ( ttl > maxTtl )
{
ttl = maxTtl;
}
ttl += g_CurrentCacheTime;
#if 0
// screening done at higher level now
//
// screen records
// - no non-RPCable types
// - no Authority records
//
if ( prr->wType != 0 )
{
prr = Dns_RecordListScreen(
prr,
SCREEN_OUT_AUTHORITY | SCREEN_OUT_NON_RPC );
DNS_ASSERT( prr );
}
#endif
//
// set timeout on all records in set
//
// note: FreeOwner handling depends on leading record
// in having owner name set, otherwise this produces
// bogus name owner fields
//
// DCR: set record list TTL function in dnslib
//
pnext = prr;
while ( pnext )
{
pnext->dwTtl = ttl;
if ( !FLAG_FreeOwner( pnext ) )
{
pnext->pName = NULL;
}
pnext = pnext->pNext;
}
}
VOID
Cache_RestoreRecordListForRpc(
IN OUT PDNS_RECORD pRecordList
)
/*++
Routine Description:
Restore cache record list for RPC.
Arguments:
pRecordList - record list to put in cache
Return Value:
None
--*/
{
PDNS_RECORD prr = pRecordList;
DWORD currentTime;
DNSDBG( TRACE, (
"Cache_RestoreRecordListForRpc( rr=%p )\n",
prr ));
if ( !prr )
{
DNS_ASSERT( FALSE );
return;
}
//
// static TTL records need no action
//
if ( IS_STATIC_RR(prr) )
{
return;
}
//
// turn timeouts back into TTLs
//
currentTime = g_CurrentCacheTime;
while ( prr )
{
DWORD ttl = prr->dwTtl - currentTime;
if ( (LONG)ttl < 0 )
{
ttl = 0;
}
prr->dwTtl = ttl;
prr = prr->pNext;
}
}
VOID
Cache_RecordSetAtomic(
IN PWSTR pwsName,
IN WORD wType,
IN PDNS_RECORD pRecordSet
)
/*++
Routine Description:
Cache record set atomically at entry.
Cache_RecordList() handles breakup of record list
and appropriate placing of records. This does caching
of single blob at particular location.
Arguments:
pRecordSet -- record list to add
Globals:
g_EntryCount -- decremented appropriately
g_NumberOfRecordsInCache -- decremented appropriately
Return Value:
None
--*/
{
INT iter;
WORD wtype;
PWSTR pname;
BOOL fstatic;
PCACHE_ENTRY pentry;
BOOL fretry;
WORD flushLevel;
DNSDBG( TRACE, (
"Cache_RecordSetAtomic( %S, type=%d, rr=%p )\n",
pwsName,
wType,
pRecordSet ));
if ( !pRecordSet )
{
return;
}
fstatic = IS_STATIC_RR(pRecordSet);
DNS_ASSERT( !fstatic ||
pRecordSet->pNext == NULL ||
(pRecordSet->wType==DNS_TYPE_CNAME) )
//
// determine caching type
// - specified OR from records
// CNAMEs will be at the head of a lookup from another type
//
wtype = wType;
if ( !wtype )
{
wtype = pRecordSet->wType;
}
//
// if name specified use it, otherwise use from records
//
pname = pwsName;
if ( !pname )
{
pname = pRecordSet->pName;
}
//
// prepare RR set for cache
//
Cache_PrepareRecordList( pRecordSet );
//
// find\create cache entry and cache
//
LOCK_CACHE();
pentry = Cache_FindEntry(
pname,
TRUE // create
);
if ( !pentry )
{
goto Failed;
}
//
// clean up existing records at node
// - remove stale records
// - remove records of same type
// - if NAME_ERROR caching remove everything
// from wire
//
flushLevel = FLUSH_LEVEL_NORMAL;
if ( wtype == DNS_TYPE_ALL &&
pRecordSet->wDataLength == 0 )
{
flushLevel = FLUSH_LEVEL_WIRE;
}
Cache_FlushEntryRecords(
pentry,
flushLevel,
wtype );
//
// check for matching record type still there
//
for ( iter = 0;
iter < (INT)pentry->MaxCount;
iter++ )
{
PDNS_RECORD prrExist = pentry->Records[iter];
if ( !prrExist ||
prrExist->wType != wtype )
{
continue;
}
// matching type still there after flush
// - if trying to cache wire set at hostfile entry, fail
DNS_ASSERT( IS_STATIC_RR(prrExist) );
if ( !fstatic )
{
DNSDBG( ANY, (
"ERROR: attempted caching at static (hosts file) record data!\n"
"\tpRecord = %p\n"
"\tName = %S\n"
"\tType = %d\n"
"\t-- Dumping new cache record list.\n",
pRecordSet,
pRecordSet->pName,
pRecordSet->wType ));
goto Failed;
}
//
// append host file records
// - start at "record" which is addr of record ptr entry
// making pNext field the actual pointer
// - delete duplicates
// - tack new RR on end
// - blow away new RR name if existing record
//
// DCR: should have simple "make cache RR set" function that
// handles name and TTL issues
//
// DCR: broken if non-flush load hits wire data; wire data
// may have multiple RR sets
//
else
{
PDNS_RECORD prr;
PDNS_RECORD prrPrev = (PDNS_RECORD) &pentry->Records[iter];
while ( prr = prrPrev->pNext )
{
// matches existing record?
// - cut existing record from list and free
if ( Dns_RecordCompare( prr, pRecordSet ) )
{
prrPrev->pNext = prr->pNext;
Dns_RecordFree( prr );
}
else
{
prrPrev = prr;
}
}
//
// tack entry on to end
// - if existing records of type delete name
//
if ( prrPrev != (PDNS_RECORD)&pentry->Records[iter] )
{
if ( IS_FREE_OWNER(pRecordSet) )
{
RECORD_HEAP_FREE( pRecordSet->pName );
pRecordSet->pName = NULL;
}
}
prrPrev->pNext = pRecordSet;
goto Done;
}
}
//
// put record into cache entry
//
// if no slot is available, switch to a harder scrub
//
// DCR: realloc if out of slots
//
fretry = FALSE;
while ( 1 )
{
for ( iter = 0;
iter < (INT)pentry->MaxCount;
iter++ )
{
if ( pentry->Records[iter] == NULL )
{
pentry->Records[iter] = pRecordSet;
g_RecordSetCount++;
g_RecordSetCache++;
goto Done;
}
}
if ( !fretry )
{
DNSDBG( QUERY, (
"No slots caching RR set %p at entry %p\n"
"\tdoing strong flush to free slot.\n",
pRecordSet,
pentry ));
Cache_FlushEntryRecords(
pentry,
FLUSH_LEVEL_WIRE,
0 );
fretry = TRUE;
continue;
}
DNSDBG( ANY, (
"ERROR: Failed to cache set %p at entry %p\n",
pRecordSet,
pentry ));
goto Failed;
}
Failed:
DNSDBG( TRACE, ( "Cache_RecordSetAtomic() => failed\n" ));
Dns_RecordListFree( pRecordSet );
Done:
UNLOCK_CACHE();
DNSDBG( TRACE, ( "Leave Cache_RecordSetAtomic()\n" ));
return;
}
VOID
Cache_RecordList(
IN OUT PDNS_RECORD pRecordList
)
/*++
Routine Description:
Cache record list.
This is cache routine for "oddball" records -- not caching under
queried name.
- hostfile
- answer records at CNAME
- additional data at additional name
Arguments:
pRecordList -- record list to cache
Return Value:
None
--*/
{
BOOL fcnameAnswer = FALSE;
PDNS_RECORD pnextRR = pRecordList;
PDNS_RECORD prr;
BOOL fstatic;
DNSDBG( TRACE, (
"Cache_RecordList( rr=%p )\n",
pRecordList ));
if ( !pRecordList )
{
return;
}
fstatic = IS_STATIC_RR(pRecordList);
//
// cache records:
// - cache additional records in query
// - cache CNAME data from query
// - cache host file data
//
// background: Glenn's caching paradigm was to cache all answer
// data at the queried name in the API call (name might be short).
// However, not caching the CNAME data can cause problems, so this
// was tacked on.
//
// For CNAME caching we throw away the CNAMEs themselves and just
// cache the actually data (address) records at the CNAME node.
//
//
// cache additional records
//
while ( prr = pnextRR )
{
BOOL fcacheSet = FALSE;
pnextRR = Dns_RecordSetDetach( prr );
//
// host file data -- always cache
//
// for CNAME want CNAME AND associated answer data
// - detach to get new next set
// - append answer data back on to CNAME for caching
// - next RR set (if exists) will be another CNAME
// to the same address data
//
// DCR: follow CNAMEs in cache
// then could pull this hack
// and avoid double building of answer data in dnsapi
//
if ( fstatic )
{
fcacheSet = TRUE;
if ( prr->wType == DNS_TYPE_CNAME &&
pnextRR &&
pnextRR->wType != DNS_TYPE_CNAME )
{
PDNS_RECORD panswer = pnextRR;
pnextRR = Dns_RecordSetDetach( panswer );
Dns_RecordListAppend( prr, panswer );
}
}
//
// wire data -- do NOT cache:
// - answer records for queried name (not CNAME)
// - CNAME records when doing caching of answer data under CNAME
// - authority section records (NS, SOA, etc)
// - OPT records
//
else if ( prr->Flags.S.Section == DNSREC_ANSWER )
{
if ( prr->wType == DNS_TYPE_CNAME )
{
fcnameAnswer = TRUE;
}
else if ( fcnameAnswer )
{
fcacheSet = TRUE;
}
}
else if ( prr->Flags.S.Section == DNSREC_ADDITIONAL )
{
if ( prr->wType != DNS_TYPE_OPT )
{
fcacheSet = TRUE;
}
}
if ( !fcacheSet )
{
Dns_RecordListFree( prr );
continue;
}
//
// cache the set
//
// flip the section field to "Answer" section
//
// DCR: section caching?
//
// note: section fields in cache indicate whether
// answer data (or additional) once out of
// cache;
// this is necessary since we cache everything
// at node and return it in one RR list; we'd
// to change must
// - return in different lists with some indication
// in cache of what's what
// OR
// - another indication of what's what
//
//if ( !fstatic )
// currently HostFile entries get answer too
{
PDNS_RECORD ptemp = prr;
while ( ptemp )
{
ptemp->Flags.S.Section = DNSREC_ANSWER;
ptemp = ptemp->pNext;
}
}
Cache_RecordSetAtomic(
NULL,
0,
prr );
}
DNSDBG( TRACE, ( "Leave Cache_RecordList()\n" ));
}
VOID
Cache_FlushRecords(
IN PWSTR pName,
IN DWORD Level,
IN WORD Type
)
/*++
Routine Description:
Flush cached records corresponding to a name and type.
Arguments:
pName -- name of records to delete
Level -- flush level
Type -- type of records to delete;
0 to flush all records at name
Return Value:
ERROR_SUCCESS if successful.
ErrorCode on failure.
--*/
{
WORD iter;
PCACHE_ENTRY pentry = NULL;
PCACHE_ENTRY pprevEntry = NULL;
DNSDBG( TRACE, (
"Cache_FlushRecords( %S, %d )\n",
pName,
Type ));
//
// lock with no-start
// - bail if no cache
//
// need this as PnP release notifications will attempt to
// flush local cache entries; this avoids rebuilding when
// already down
//
LOCK_CACHE_NO_START();
if ( !g_HashTable )
{
goto Done;
}
//
// find entry in cache
//
pentry = Cache_FindEntry(
pName,
FALSE // no create
);
if ( !pentry )
{
goto Done;
}
//
// flush records of type
// - zero type will flush all
//
// note: Cache_FindEntry() always moves the found entry
// to the front of the hash bucket list; this allows
// us to directly whack the entry
//
if ( Cache_FlushEntryRecords(
pentry,
Level,
Type ) )
{
DWORD index = getHashIndex(
pentry->pName,
0 );
DNS_ASSERT( pentry == g_HashTable[index] );
if ( pentry == g_HashTable[index] )
{
g_HashTable[ index ] = pentry->pNext;
Cache_FreeEntry( pentry );
}
}
Done:
UNLOCK_CACHE();
}
#if 0
BOOL
ReadCachedResults(
OUT PDNS_RESULTS pResults,
IN PWSTR pwsName,
IN WORD wType
)
/*++
Routine Description:
Find records of given name and type in cache.
Arguments:
pResults -- addr to receive results
pwsName -- name
wType -- record type to find
Return Value:
TRUE if results found.
FALSE if no cached data for name and type.
--*/
{
PDNS_RECORD prr;
DNS_STATUS status;
BOOL found = FALSE;
//
// clear results
//
RtlZeroMemory( pResults, sizeof(*pResults) );
// get cache results
// break out into results buffer
if ( found )
{
BreakRecordsIntoBlob(
pResults,
prr,
wType );
pResults->Status = status;
}
return( found );
}
#endif
//
// Cache utilities for remote routines
//
PDNS_RECORD
Cache_FindRecordsPrivate(
IN PWSTR pwsName,
IN WORD wType
)
/*++
Routine Description:
Find records of given name and type in cache.
Arguments:
pwsName -- name
Type -- record type to find
Return Value:
Ptr to record set of desired type -- if found.
NULL if not found.
--*/
{
PCACHE_ENTRY pentry;
PDNS_RECORD prr = NULL;
DNSDBG( TRACE, (
"Cache_FindRecordsPrivate( %S, type=%d )\n",
pwsName,
wType ));
LOCK_CACHE();
pentry = Cache_FindEntry(
pwsName,
FALSE );
if ( pentry )
{
prr = Cache_FindEntryRecords(
pentry,
wType );
}
UNLOCK_CACHE();
DNSDBG( TRACE, (
"Leave Cache_FindRecordsPrivate( %S, type=%d ) => %p\n",
pwsName,
wType,
prr ));
return prr;
}
BOOL
Cache_GetRecordsForRpc(
OUT PDNS_RECORD * ppRecordList,
OUT PDNS_STATUS pStatus,
IN PWSTR pwsName,
IN WORD wType,
IN DWORD Flags
)
/*++
Routine Description:
Find records of given name and type in cache.
Arguments:
ppRecordList -- addr to receive pointer to record list
pStatus -- addr to get status return
pwsName -- name
Type -- record type to find
Flags -- query flags
Return Value:
TRUE if cache hit. OUT params are valid.
FALSE if cache miss. OUT params are unset.
--*/
{
PDNS_RECORD prr;
PDNS_RECORD prrResult = NULL;
DNS_STATUS status = NO_ERROR;
DNSDBG( RPC, (
"Cache_GetRecordsForRpc( %S, t=%d )\n",
pwsName,
wType ));
if ( (Flags & DNS_QUERY_BYPASS_CACHE) &&
(Flags & DNS_QUERY_NO_HOSTS_FILE) )
{
return FALSE;
}
LOCK_CACHE();
//
// check cache for name and type
// - if name or type missing, jump to wire lookup
//
prr = Cache_FindRecordsPrivate(
pwsName,
wType );
if ( !prr )
{
goto Failed;
}
//
// cache hit
//
// if only interested in host file data ignore
//
if ( IS_HOSTS_FILE_RR(prr) )
{
if ( Flags & DNS_QUERY_NO_HOSTS_FILE )
{
goto Failed;
}
}
else // cache data
{
if ( Flags & DNS_QUERY_BYPASS_CACHE )
{
goto Failed;
}
}
//
// build response from cache data
// - cached NAME_ERROR or empty
// - cached records
//
if ( prr->wDataLength == 0 )
{
status = (prr->wType == DNS_TYPE_ANY)
? DNS_ERROR_RCODE_NAME_ERROR
: DNS_INFO_NO_RECORDS;
}
else
{
// for CNAME query, get only the CNAME record itself
// not the data at the CNAME
//
// DCR: CNAME handling should be optional -- not given
// for cache display purposes
//
if ( wType == DNS_TYPE_CNAME &&
prr->wType == DNS_TYPE_CNAME &&
prr->Flags.S.Section == DNSREC_ANSWER )
{
prrResult = Dns_RecordCopyEx(
prr,
DnsCharSetUnicode,
DnsCharSetUnicode );
}
else
{
prrResult = Dns_RecordSetCopyEx(
prr,
DnsCharSetUnicode,
DnsCharSetUnicode );
}
if ( prrResult )
{
Cache_RestoreRecordListForRpc( prrResult );
status = ERROR_SUCCESS;
}
else
{
status = ERROR_NOT_ENOUGH_MEMORY;
}
}
UNLOCK_CACHE();
// set return values
*ppRecordList = prrResult;
*pStatus = status;
return TRUE;
Failed:
UNLOCK_CACHE();
return FALSE;
}
//
// Garbage collection
//
VOID
Cache_SizeCheck(
VOID
)
/*++
Routine Description:
Check cache size.
Arguments:
Flag -- flag, currently unused
Return Value:
None
--*/
{
//
// ok -- don't signal for garbage collect
//
// - below threshold
// - already in garbage collection
// - collected recently
//
if ( g_RecordSetCount < g_RecordSetCountThreshold ||
g_GarbageCollectFlag ||
g_NextGarbageTime > GetCurrentTimeInSeconds() )
{
return;
}
DNSDBG( CACHE, (
"Cache_SizeCheck() over threshold!\n"
"\tRecordSetCount = %d\n"
"\tRecordSetCountLimit = %d\n"
"\tStarting garbage collection ...\n",
g_RecordSetCount,
g_RecordSetCountThreshold ));
//
// signal within lock, so that service thread
// can do signal within lock and avoid race on StopFlag check
// obviously better to simply not overload lock
//
LOCK_CACHE();
if ( !g_StopFlag )
{
g_GarbageCollectFlag = TRUE;
SetEvent( g_hStopEvent );
}
UNLOCK_CACHE();
}
VOID
Cache_GarbageCollect(
IN DWORD Flag
)
/*++
Routine Description:
Garbage collect cache.
Arguments:
Flag -- flag, currently unused
Return Value:
None
--*/
{
DWORD iter;
DWORD index;
WORD flushLevel;
DWORD passCount;
DNSDBG( CACHE, (
"Cache_GarbageCollect()\n"
"\tNextIndex = %d\n"
"\tRecordSetCount = %d\n"
"\tRecordSetLimit = %d\n"
"\tRecordSetThreshold = %d\n",
g_NextGarbageIndex,
g_RecordSetCount,
g_RecordSetCountLimit,
g_RecordSetCountThreshold
));
if ( !g_HashTable )
{
return;
}
//
// collect timed out data in cache
//
// DCR: smart garbage detect
// - cleans until below limit
// - first pass invalid
// - then the hard stuff
// use restartable index so get through the cach
//
passCount = 0;
while ( 1 )
{
if ( passCount == 0 )
{
flushLevel = FLUSH_LEVEL_INVALID;
}
else if ( passCount == 1 )
{
flushLevel = FLUSH_LEVEL_GARBAGE;
}
else
{
break;
}
passCount++;
//
// flush all hash bins at current flush level
// until
// - service stop
// - push cache size below limit
//
for ( iter = 0;
iter < g_HashTableSize;
iter++ )
{
index = (iter + g_NextGarbageIndex) % g_HashTableSize;
if ( g_StopFlag ||
g_WakeFlag ||
g_RecordSetCount < g_RecordSetCountLimit )
{
passCount = MAXDWORD;
break;
}
Cache_FlushBucket(
index,
flushLevel );
}
index++;
if ( index >= g_HashTableSize )
{
index = 0;
}
g_NextGarbageIndex = index;
}
//
// reset garbage globals
// - lockout for interval
// - clear signal flag
// - reset event (if not shuttting down)
//
// note: reset signal within lock, so that service thread
// can do signal within lock and avoid race on StopFlag check
// obviously better to simply not overload lock
//
g_NextGarbageTime = GetCurrentTimeInSeconds() + GARBAGE_LOCKOUT_INTERVAL;
LOCK_CACHE();
if ( !g_StopFlag )
{
g_GarbageCollectFlag = FALSE;
ResetEvent( g_hStopEvent );
}
UNLOCK_CACHE();
DNSDBG( CACHE, (
"Leave Cache_GarbageCollect()\n"
"\tNextIndex = %d\n"
"\tNextTime = %d\n"
"\tRecordSetCount = %d\n"
"\tRecordSetLimit = %d\n"
"\tRecordSetThreshold = %d\n",
g_NextGarbageIndex,
g_NextGarbageTime,
g_RecordSetCount,
g_RecordSetCountLimit,
g_RecordSetCountThreshold
));
}
//
// Hostfile load stuff
//
VOID
LoadHostFileIntoCache(
IN PSTR pszFileName
)
/*++
Routine Description:
Read hosts file into cache.
Arguments:
pFileName -- file name to load
Return Value:
None.
--*/
{
HOST_FILE_INFO hostInfo;
DNSDBG( INIT, ( "Enter LoadHostFileIntoCache\n" ));
//
// read entries from host file until exhausted
// - cache A record for each name and alias
// - cache PTR to name
//
RtlZeroMemory(
&hostInfo,
sizeof(hostInfo) );
hostInfo.pszFileName = pszFileName;
if ( !Dns_OpenHostFile( &hostInfo ) )
{
return;
}
hostInfo.fBuildRecords = TRUE;
while ( Dns_ReadHostFileLine( &hostInfo ) )
{
// cache all the records we sucked out
Cache_RecordList( hostInfo.pForwardRR );
Cache_RecordList( hostInfo.pReverseRR );
Cache_RecordList( hostInfo.pAliasRR );
}
Dns_CloseHostFile( &hostInfo );
DNSDBG( INIT, ( "Leave LoadHostFileIntoCache\n" ));
}
VOID
InitCacheWithHostFile(
VOID
)
/*++
Routine Description:
Initialize cache with host(s) file.
This handles regular cache file and ICS file if it
exists.
Arguments:
None
Return Value:
None.
--*/
{
DNSDBG( INIT, ( "Enter InitCacheWithHostFile\n" ));
//
// load host file into cache
//
LoadHostFileIntoCache( NULL );
//
// if running ICS, load it's file also
//
LoadHostFileIntoCache( "hosts.ics" );
DNSDBG( INIT, ( "Leave InitCacheWithHostFile\n\n\n" ));
}
DNS_STATUS
Cache_QueryResponse(
IN OUT PQUERY_BLOB pBlob
)
/*++
Routine Description:
Find records of given name and type in cache.
Arguments:
pBlob -- query blob
Uses:
pwsName
wType
Status
pRecords
fCacheNegativeResponse
Sets:
pRecords - may be reset to exclude non-RPCable records
Return Value:
ErrorStatus -- same as query status, unless processing error during caching
--*/
{
DNS_STATUS status = pBlob->Status;
PWSTR pname = pBlob->pNameOrig;
WORD wtype = pBlob->wType;
PDNS_RECORD presultRR = pBlob->pRecords;
DNSDBG( RPC, (
"\nCache_QueryResponse( %S, type %d )\n",
pname,
wtype ));
//
// successful response
// - make copy of records to return to caller
// - cache actual query record set
// - make copy to cache any additional data
//
if ( status == ERROR_SUCCESS && presultRR )
{
DWORD copyFlag;
PDNS_RECORD prrCache;
// cleanup for RPC and caching
prrCache = Dns_RecordListScreen(
presultRR,
SCREEN_OUT_AUTHORITY | SCREEN_OUT_NON_RPC );
//
// make copy for return
// - don't include authority records
//
// NOTE: IMPORTANT
// we return (RPC) a COPY of the wire set and cache the
// wire set; this is because the wire set has imbedded data
// (the data pointers are not actual heap allocations) and
// and hence can not be RPC'd (without changing the RPC
// definition to flat data)
//
// if we later want to return authority data on first query,
// then
// - clean non-RPC only
// - including owner name fixups
// - copy for result set
// - clean original for authority -- cache
// - clean any additional -- cache
//
// note: do name pointer fixup by making round trip into cache format
//
// DCR: shouldn't have external name pointers anywhere
// DCR: do RPC-able cleanup on original set before copy
// OR
// DCR: have "cache state" on record
// then could move original results to cache state and caching
// routines could detect state and avoid double TTLing
presultRR = Dns_RecordListCopyEx(
prrCache,
0,
// SCREEN_OUT_AUTHORITY
DnsCharSetUnicode,
DnsCharSetUnicode );
pBlob->pRecords = presultRR;
if ( !presultRR )
{
Dns_RecordListFree( prrCache );
status = DNS_ERROR_NO_MEMORY;
goto Done;
}
// name pointer fixup
Cache_PrepareRecordList( presultRR );
Cache_RestoreRecordListForRpc( presultRR );
//
// do NOT cache local records
//
// note: we went through this function only to get
// PTR records and CNAME records in RPC format
// (no imbedded pointers)
//
if ( pBlob->pLocalRecords )
{
Dns_RecordListFree( prrCache );
goto Done;
}
//
// cache original data
//
if ( prrCache )
{
Cache_RecordSetAtomic(
pname,
wtype,
prrCache );
}
//
// extra records
// - additional data
// - CNAME answer data to cache at CNAME itself
// in CNAME case must include ANSWER data, but
// skip the CNAME itself
//
// Cache_RecordList() breaks records into RR sets before caching
//
prrCache = presultRR;
copyFlag = SCREEN_OUT_ANSWER | SCREEN_OUT_AUTHORITY;
if ( prrCache->wType == DNS_TYPE_CNAME )
{
prrCache = prrCache->pNext;
copyFlag = SCREEN_OUT_AUTHORITY;
}
prrCache = Dns_RecordListCopyEx(
prrCache,
copyFlag,
DnsCharSetUnicode,
DnsCharSetUnicode );
if ( prrCache )
{
Cache_RecordList( prrCache );
}
}
//
// negative response
//
else if ( status == DNS_ERROR_RCODE_NAME_ERROR ||
status == DNS_INFO_NO_RECORDS )
{
DWORD ttl;
PDNS_RECORD prr;
if ( !pBlob->fCacheNegative )
{
DNSDBG( QUERY, (
"No negative caching for %S, type=%d\n",
pname, wtype ));
goto Done;
}
//
// create negative cache entry
//
// DCR: should use TTL returned in SOA
//
prr = Dns_AllocateRecord( 0 );
if ( !prr )
{
status = ERROR_NOT_ENOUGH_MEMORY;
goto Done;
}
prr->pName = (PWSTR) Dns_StringCopyAllocate(
(PCHAR) pname,
0, // NULL terminated
DnsCharSetUnicode,
DnsCharSetUnicode );
if ( prr->pName )
{
SET_FREE_OWNER( prr );
}
prr->wDataLength = 0;
ttl = g_MaxNegativeCacheTtl;
if ( wtype == DNS_TYPE_SOA
&&
ttl > g_NegativeSOACacheTime )
{
ttl = g_NegativeSOACacheTime;
}
prr->dwTtl = ttl;
prr->Flags.S.CharSet = DnsCharSetUnicode;
prr->Flags.S.Section = DNSREC_ANSWER;
prr->Flags.DW |= DNSREC_NOEXIST;
if ( status == DNS_ERROR_RCODE_NAME_ERROR )
{
prr->wType = DNS_TYPE_ANY;
}
else
{
prr->wType = wtype;
}
Cache_RecordSetAtomic(
NULL, // default name
0, // default type
prr );
}
// failure return from query
// - nothing to cache
else
{
DNSDBG( QUERY, (
"Uncacheable error code %d -- no caching for %S, type=%d\n",
status,
pname,
wtype ));
}
Done:
//
// check cache size to see if garbage collect necessary
//
// note we do this only on query caching; this avoids
// - jamming ourselves in hosts file load
// - wakeup and grabbing lock between separate sets of query response
//
Cache_SizeCheck();
return status;
}
//
// End ncache.c
//