diff --git a/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs b/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs index 516c2d56..ca72a772 100644 --- a/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs +++ b/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs @@ -41,11 +41,17 @@ namespace DnsServerCore.Dns.ZoneManagers public const uint MINIMUM_RECORD_TTL = 10u; public const uint MAXIMUM_RECORD_TTL = 7 * 24 * 60 * 60; public const uint SERVE_STALE_TTL = 3 * 24 * 60 * 60; //3 days serve stale ttl as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion + public const uint SERVE_STALE_ANSWER_TTL = 30; //as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion + public const uint SERVE_STALE_RESET_TTL = 30; //as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion + + const uint SERVE_STALE_MIN_RESET_TTL = 10; + const uint SERVE_STALE_MAX_RESET_TTL = 900; readonly DnsServer _dnsServer; readonly CacheZoneTree _root = new CacheZoneTree(); + uint _serveStaleResetTtl = SERVE_STALE_RESET_TTL; long _maximumEntries; long _totalEntries; @@ -54,7 +60,7 @@ namespace DnsServerCore.Dns.ZoneManagers #region constructor public CacheZoneManager(DnsServer dnsServer) - : base(FAILURE_RECORD_TTL, NEGATIVE_RECORD_TTL, MINIMUM_RECORD_TTL, MAXIMUM_RECORD_TTL, SERVE_STALE_TTL) + : base(FAILURE_RECORD_TTL, NEGATIVE_RECORD_TTL, MINIMUM_RECORD_TTL, MAXIMUM_RECORD_TTL, SERVE_STALE_TTL, SERVE_STALE_ANSWER_TTL) { _dnsServer = dnsServer; } @@ -687,7 +693,7 @@ namespace DnsServerCore.Dns.ZoneManagers return null; } - public override DnsDatagram Query(DnsDatagram request, bool serveStaleAndResetExpiry = false, bool findClosestNameServers = false) + public override DnsDatagram Query(DnsDatagram request, bool serveStale = false, bool findClosestNameServers = false, bool resetExpiry = false) { DnsQuestionRecord question = request.Question[0]; @@ -721,7 +727,7 @@ namespace DnsServerCore.Dns.ZoneManagers if (zone is not null) { //zone found - IReadOnlyList answer = zone.QueryRecords(question.Type, serveStaleAndResetExpiry, false, eDnsClientSubnet, advancedForwardingClientSubnet); + IReadOnlyList answer = zone.QueryRecords(question.Type, serveStale, false, eDnsClientSubnet, advancedForwardingClientSubnet); if (answer.Count > 0) { //answer found in cache @@ -738,24 +744,24 @@ namespace DnsServerCore.Dns.ZoneManagers } } - if (serveStaleAndResetExpiry) + if (resetExpiry) { if (firstRR.IsStale) - firstRR.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 + firstRR.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 if (dnsSpecialCacheRecord.Authority is not null) { foreach (DnsResourceRecord record in dnsSpecialCacheRecord.Authority) { if (record.IsStale) - record.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 + record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 } } } IReadOnlyList specialOptions; - if (firstRR.WasExpiryReset) + if (firstRR.WasExpiryReset || firstRR.IsStale) { List newOptions = new List(dnsSpecialCacheRecord.EDnsOptions.Count + 1); @@ -832,7 +838,7 @@ namespace DnsServerCore.Dns.ZoneManagers List newAnswers = new List(answer.Count + 3); newAnswers.AddRange(answer); - ResolveCNAME(question, lastRR, serveStaleAndResetExpiry, eDnsClientSubnet, advancedForwardingClientSubnet, newAnswers); + ResolveCNAME(question, lastRR, serveStale, eDnsClientSubnet, advancedForwardingClientSubnet, newAnswers); answer = newAnswers; } @@ -864,18 +870,16 @@ namespace DnsServerCore.Dns.ZoneManagers case DnsResourceRecordType.SRV: case DnsResourceRecordType.SVCB: case DnsResourceRecordType.HTTPS: - additional = GetAdditionalRecords(answer, serveStaleAndResetExpiry, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet); + additional = GetAdditionalRecords(answer, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet); break; } - IReadOnlyList options = null; - - if (serveStaleAndResetExpiry) + if (resetExpiry) { foreach (DnsResourceRecord record in answer) { if (record.IsStale) - record.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 + record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 } if (additional is not null) @@ -883,21 +887,19 @@ namespace DnsServerCore.Dns.ZoneManagers foreach (DnsResourceRecord record in additional) { if (record.IsStale) - record.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 + record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 } } - - options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; } - else + + IReadOnlyList options = null; + + foreach (DnsResourceRecord record in answer) { - foreach (DnsResourceRecord record in answer) + if (record.WasExpiryReset || record.IsStale) { - if (record.WasExpiryReset) - { - options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; - break; - } + options = [new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null))]; + break; } } @@ -948,12 +950,12 @@ namespace DnsServerCore.Dns.ZoneManagers //check for DNAME in closest zone if (closest is not null) { - IReadOnlyList answer = closest.QueryRecords(DnsResourceRecordType.DNAME, serveStaleAndResetExpiry, true, eDnsClientSubnet, advancedForwardingClientSubnet); + IReadOnlyList answer = closest.QueryRecords(DnsResourceRecordType.DNAME, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet); if ((answer.Count > 0) && (answer[0].Type == DnsResourceRecordType.DNAME)) { DnsResponseCode rCode; - if (DoDNAMESubstitution(question, answer, serveStaleAndResetExpiry, eDnsClientSubnet, advancedForwardingClientSubnet, out answer)) + if (DoDNAMESubstitution(question, answer, serveStale, eDnsClientSubnet, advancedForwardingClientSubnet, out answer)) rCode = DnsResponseCode.NoError; else rCode = DnsResponseCode.YXDomain; @@ -976,27 +978,23 @@ namespace DnsServerCore.Dns.ZoneManagers ednsFlags = EDnsHeaderFlags.DNSSEC_OK; } - EDnsOption[] options = null; - - if (serveStaleAndResetExpiry) + if (resetExpiry) { foreach (DnsResourceRecord record in answer) { if (record.IsStale) - record.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 + record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767 } - - options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; } - else + + EDnsOption[] options = null; + + foreach (DnsResourceRecord record in answer) { - foreach (DnsResourceRecord record in answer) + if (record.WasExpiryReset || record.IsStale) { - if (record.WasExpiryReset) - { - options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; - break; - } + options = [new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, null))]; + break; } } @@ -1026,23 +1024,23 @@ namespace DnsServerCore.Dns.ZoneManagers while (true) { - IReadOnlyList closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStaleAndResetExpiry, true, eDnsClientSubnet, advancedForwardingClientSubnet); + IReadOnlyList closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet); if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS) && (closestAuthority[0].Name.Length > 0)) //dont trust root name servers from cache! { if (dnssecOk) { if (closestAuthority[0].DnssecStatus != DnssecStatus.Disabled) //dont return records with disabled status { - closestAuthority = AddDSRecordsTo(delegation, serveStaleAndResetExpiry, closestAuthority, eDnsClientSubnet, advancedForwardingClientSubnet); + closestAuthority = AddDSRecordsTo(delegation, serveStale, closestAuthority, eDnsClientSubnet, advancedForwardingClientSubnet); - IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStaleAndResetExpiry, true, eDnsClientSubnet, advancedForwardingClientSubnet); + IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, closestAuthority[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional); } } else { - IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStaleAndResetExpiry, false, eDnsClientSubnet, advancedForwardingClientSubnet); + IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStale, false, eDnsClientSubnet, advancedForwardingClientSubnet); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional); } @@ -1145,6 +1143,18 @@ namespace DnsServerCore.Dns.ZoneManagers #region properties + public uint ServeStaleResetTtl + { + get { return _serveStaleResetTtl; } + set + { + if ((value < SERVE_STALE_MIN_RESET_TTL) || (value > SERVE_STALE_MAX_RESET_TTL)) + throw new ArgumentOutOfRangeException(nameof(ServeStaleResetTtl), "Serve stale reset TTL must be between " + SERVE_STALE_MIN_RESET_TTL + " and " + SERVE_STALE_MAX_RESET_TTL + " seconds. Recommended value is 30 seconds."); + + _serveStaleResetTtl = value; + } + } + public long MaximumEntries { get { return _maximumEntries; }