DnsServer: implemented draft-ietf-dnsop-serve-stale-04 in RecursiveResolve().

This commit is contained in:
Shreyas Zare
2019-03-30 17:07:48 +05:30
parent bab78ec014
commit ea5a1aab45

View File

@@ -70,7 +70,7 @@ namespace DnsServerCore
X509Certificate2 _certificate; X509Certificate2 _certificate;
readonly Zone _authoritativeZoneRoot = new Zone(true); readonly Zone _authoritativeZoneRoot = new Zone(true);
readonly Zone _cacheZoneRoot = new Zone(false); readonly Zone _cacheZoneRoot = new Zone(false) { ServeStaleTtl = 7 * 24 * 60 * 60 }; //7 days serve stale ttl as per draft-ietf-dnsop-serve-stale-04
readonly Zone _allowedZoneRoot = new Zone(true); readonly Zone _allowedZoneRoot = new Zone(true);
Zone _blockedZoneRoot = new Zone(true); Zone _blockedZoneRoot = new Zone(true);
@@ -83,7 +83,7 @@ namespace DnsServerCore
DnsTransportProtocol _forwarderProtocol = DnsTransportProtocol.Udp; DnsTransportProtocol _forwarderProtocol = DnsTransportProtocol.Udp;
DnsTransportProtocol _recursiveResolveProtocol = DnsTransportProtocol.Udp; DnsTransportProtocol _recursiveResolveProtocol = DnsTransportProtocol.Udp;
bool _preferIPv6 = false; bool _preferIPv6 = false;
int _retries = 3; int _retries = 2;
int _timeout = 2000; int _timeout = 2000;
int _maxStackCount = 10; int _maxStackCount = 10;
LogManager _log; LogManager _log;
@@ -93,7 +93,7 @@ namespace DnsServerCore
int _tcpSendTimeout = 10000; int _tcpSendTimeout = 10000;
int _tcpReceiveTimeout = 10000; int _tcpReceiveTimeout = 10000;
readonly ConcurrentDictionary<DnsQuestionRecord, object> _recursiveQueryLocks = new ConcurrentDictionary<DnsQuestionRecord, object>(); readonly ConcurrentDictionary<DnsQuestionRecord, RecursiveQueryLock> _recursiveQueryLocks = new ConcurrentDictionary<DnsQuestionRecord, RecursiveQueryLock>(Environment.ProcessorCount * 64, Environment.ProcessorCount * 32);
volatile ServiceState _state = ServiceState.Stopped; volatile ServiceState _state = ServiceState.Stopped;
@@ -1119,76 +1119,140 @@ namespace DnsServerCore
{ {
//query cache zone to see if answer available //query cache zone to see if answer available
{ {
DnsDatagram cacheResponse = QueryCache(request); DnsDatagram cacheResponse = QueryCache(request, false, true);
if (cacheResponse != null) if (cacheResponse != null)
return cacheResponse; return cacheResponse;
} }
//recursion with locking //recursion with locking
RecursiveQueryLock newLockObj = new RecursiveQueryLock();
RecursiveQueryLock actualLockObj = _recursiveQueryLocks.GetOrAdd(request.Question[0], newLockObj);
if (actualLockObj.Equals(newLockObj))
{ {
object newLockObj = new object(); //got lock so question not being resolved; do recursive resolution in worker thread
object actualLockObj = _recursiveQueryLocks.GetOrAdd(request.Question[0], newLockObj); ThreadPool.QueueUserWorkItem(delegate (object state)
if (!actualLockObj.Equals(newLockObj))
{ {
//question already being recursively resolved by another thread, wait till timeout or pulse signal //select protocol
lock (actualLockObj) DnsTransportProtocol protocol;
if ((viaNameServers == null) && (_forwarders != null))
{ {
Monitor.Wait(actualLockObj, _timeout * _retries); viaNameServers = _forwarders;
protocol = _forwarderProtocol;
}
else
{
protocol = _recursiveResolveProtocol;
} }
//query cache zone again to see if answer available try
{ {
DnsDatagram cacheResponse = QueryCache(request); //recursive resolve and update cache
if (cacheResponse != null) DnsClient.RecursiveResolve(request.Question[0], viaNameServers, _dnsCache, _proxy, _preferIPv6, protocol, _retries, _timeout, _recursiveResolveProtocol, _maxStackCount);
return cacheResponse;
} }
catch (Exception ex)
{
LogManager log = _log;
if (log != null)
{
string nameServers = null;
//no response available in cache so respond with server failure if (viaNameServers != null)
return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, true, false, false, DnsResponseCode.ServerFailure, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); {
foreach (NameServerAddress nameServer in viaNameServers)
{
if (nameServers == null)
nameServers = nameServer.ToString();
else
nameServers += ", " + nameServer.ToString();
}
}
log.Write("DNS Server recursive resolution failed for QNAME: " + request.Question[0].Name + "; QTYPE: " + request.Question[0].Type.ToString() + "; QCLASS: " + request.Question[0].Class.ToString() + (nameServers == null ? "" : "; Name Servers: " + nameServers) + ";\r\n" + ex.ToString());
}
//fetch stale record and reset expiry
{
DnsDatagram cacheResponse = QueryCache(request, true, false);
if (cacheResponse != null)
{
foreach (DnsResourceRecord record in cacheResponse.Answer)
{
if (record.IsStale)
record.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per draft-ietf-dnsop-serve-stale-04
}
}
}
}
finally
{
//remove question lock
if (_recursiveQueryLocks.TryRemove(request.Question[0], out RecursiveQueryLock lockObj))
{
//pulse all waiting threads
lock (lockObj)
{
lockObj.SetComplete();
Monitor.PulseAll(lockObj);
}
}
}
});
}
//request is being recursively resolved by worker thread
bool timeout = false;
//wait till short timeout or pulse signal
lock (actualLockObj)
{
if (!actualLockObj.Complete)
timeout = !Monitor.Wait(actualLockObj, _timeout - 200); //1.8 sec wait with default client timeout as 2 sec as per draft-ietf-dnsop-serve-stale-04
}
//query cache zone to get the cached answer
{
//if there was timeout then return stale answer (if available) as per draft-ietf-dnsop-serve-stale-04
DnsDatagram cacheResponse = QueryCache(request, timeout, timeout);
if (cacheResponse != null)
return cacheResponse;
}
if (timeout)
{
//wait till timeout or pulse signal for some more time before responding as ServerFailure
//this is required since, quickly returning ServerFailure results in clients giving up lookup attempt early causing DNS error messages in web browsers
lock (actualLockObj)
{
if (!actualLockObj.Complete)
Monitor.Wait(actualLockObj, _timeout + 200);
}
//query cache zone to see if answer is available now
{
DnsDatagram cacheResponse = QueryCache(request, false, false);
if (cacheResponse != null)
return cacheResponse;
} }
} }
//select protocol //no response available in cache so respond with ServerFailure
DnsTransportProtocol protocol; return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, true, false, false, DnsResponseCode.ServerFailure, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null);
if ((viaNameServers == null) && (_forwarders != null))
{
viaNameServers = _forwarders;
protocol = _forwarderProtocol;
}
else
{
protocol = _recursiveResolveProtocol;
}
try
{
return DnsClient.RecursiveResolve(request.Question[0], viaNameServers, _dnsCache, _proxy, _preferIPv6, protocol, _retries, _timeout, _recursiveResolveProtocol, _maxStackCount);
}
finally
{
//remove question lock
if (_recursiveQueryLocks.TryRemove(request.Question[0], out object lockObj))
{
//pulse all waiting threads
lock (lockObj)
{
Monitor.PulseAll(lockObj);
}
}
}
} }
private DnsDatagram QueryCache(DnsDatagram request) private DnsDatagram QueryCache(DnsDatagram request, bool serveStale, bool tagAsCacheHit)
{ {
DnsDatagram cacheResponse = _cacheZoneRoot.Query(request); DnsDatagram cacheResponse = _cacheZoneRoot.Query(request, serveStale);
if (cacheResponse.Header.RCODE != DnsResponseCode.Refused) if (cacheResponse.Header.RCODE != DnsResponseCode.Refused)
{ {
if ((cacheResponse.Answer.Length > 0) || (cacheResponse.Authority.Length == 0) || (cacheResponse.Authority[0].Type == DnsResourceRecordType.SOA)) if ((cacheResponse.Answer.Length > 0) || (cacheResponse.Authority.Length == 0) || (cacheResponse.Authority[0].Type == DnsResourceRecordType.SOA))
{ {
cacheResponse.Tag = "cacheHit"; if (tagAsCacheHit)
cacheResponse.Tag = "cacheHit";
return cacheResponse; return cacheResponse;
} }
} }
@@ -1596,13 +1660,21 @@ namespace DnsServerCore
public int Retries public int Retries
{ {
get { return _retries; } get { return _retries; }
set { _retries = value; } set
{
if (value > 0)
_retries = value;
}
} }
public int Timeout public int Timeout
{ {
get { return _timeout; } get { return _timeout; }
set { _timeout = value; } set
{
if (value >= 2000)
_timeout = value;
}
} }
public int MaxStackCount public int MaxStackCount
@@ -1674,5 +1746,30 @@ namespace DnsServerCore
#endregion #endregion
} }
class RecursiveQueryLock
{
#region variables
bool _complete;
#endregion
#region public
public void SetComplete()
{
_complete = true;
}
#endregion
#region properties
public bool Complete
{ get { return _complete; } }
#endregion
}
} }
} }