@@ -464,10 +464,24 @@ [ret makeImmutable]; return ret; } + +static bool +containsExpiredRecord(OFDNSResponseRecords responseRecords, uint32_t age) +{ + OFEnumerator *enumerator = [responseRecords objectEnumerator]; + OFArray OF_GENERIC(OFDNSResourceRecord *) *records; + + while ((records = [enumerator nextObject]) != nil) + for (OFDNSResourceRecord *record in records) + if (record.TTL < age) + return true; + + return false; +} @implementation OFDNSResolverContext - (instancetype)initWithQuery: (OFDNSQuery *)query ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings @@ -576,10 +590,11 @@ @try { _settings = [[OFDNSResolverSettings alloc] init]; _queries = [[OFMutableDictionary alloc] init]; _TCPQueries = [[OFMutableDictionary alloc] init]; + _cache = [[OFMutableDictionary alloc] init]; [_settings reload]; } @catch (id e) { [self release]; @throw e; @@ -599,10 +614,11 @@ [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; #endif [_queries release]; [_TCPQueries release]; + [_cache release]; [super dealloc]; } - (OFDictionary *)staticHosts @@ -802,10 +818,29 @@ delegate: (id )delegate { void *pool = objc_autoreleasePoolPush(); OFNumber *ID; OFDNSResolverContext *context; + OFPair OF_GENERIC(OFDate *, OFDNSResponse *) *cacheEntry; + + if ((cacheEntry = [_cache objectForKey: query]) != nil) { + uint32_t age = + (uint32_t)-[cacheEntry.firstObject timeIntervalSinceNow]; + OFDNSResponse *response = cacheEntry.secondObject; + + if (!containsExpiredRecord(response.answerRecords, age) && + !containsExpiredRecord(response.authorityRecords, age) && + !containsExpiredRecord(response.additionalRecords, age)) { + [delegate resolver: self + didPerformQuery: query + response: response + exception: nil]; + + objc_autoreleasePoolPop(pool); + return; + } + } /* Random, unused ID */ do { ID = [OFNumber numberWithUnsignedShort: OFRandom16()]; } while ([_queries objectForKey: ID] != nil); @@ -1038,10 +1073,17 @@ } if (exception != nil) response = nil; + if (response != nil) + [_cache setObject: [OFPair pairWithFirstObject: [OFDate date] + secondObject: response] + forKey: context->_query]; + else + [_cache removeObjectForKey: context->_query]; + [context->_delegate resolver: self didPerformQuery: context->_query response: response exception: exception];