mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3062 lines
62 KiB
3062 lines
62 KiB
/*++
|
|
|
|
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
|
|
//
|