/*++ 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 //