3063 lines
62 KiB
C
3063 lines
62 KiB
C
/*++
|
||
|
||
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
|
||
//
|