diff --git a/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs b/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs index c5987bcb..f18f3407 100644 --- a/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs +++ b/DnsServerCore/Dns/ZoneManagers/CacheZoneManager.cs @@ -198,6 +198,51 @@ namespace DnsServerCore.Dns.ZoneManagers return nsRecords; } + private static void AddRRSIGRecords(IReadOnlyList answer, out IReadOnlyList newAnswer, out IReadOnlyList newAuthority) + { + List newAnswerList = new List(answer.Count * 2); + List newAuthorityList = null; + + foreach (DnsResourceRecord record in answer) + { + newAnswerList.Add(record); + + DnsResourceRecordInfo rrInfo = record.GetRecordInfo(); + + IReadOnlyList rrsigRecords = rrInfo.RRSIGRecords; + if (rrsigRecords is not null) + { + newAnswerList.AddRange(rrsigRecords); + + foreach (DnsResourceRecord rrsigRecord in rrsigRecords) + { + if (!DnsRRSIGRecordData.IsWildcard(rrsigRecord)) + continue; + + //add NSEC/NSEC3 for the wildcard proof + if (newAuthorityList is null) + newAuthorityList = new List(2); + + IReadOnlyList nsecRecords = rrInfo.NSECRecords; + if (nsecRecords is not null) + { + foreach (DnsResourceRecord nsecRecord in nsecRecords) + { + newAuthorityList.Add(nsecRecord); + + IReadOnlyList nsecRRSIGRecords = nsecRecord.GetRecordInfo().RRSIGRecords; + if (nsecRRSIGRecords is not null) + newAuthorityList.AddRange(nsecRRSIGRecords); + } + } + } + } + } + + newAnswer = newAnswerList; + newAuthority = newAuthorityList; + } + private void ResolveCNAME(DnsQuestionRecord question, DnsResourceRecord lastCNAME, bool serveStale, List answerRecords) { int queryCount = 0; @@ -456,21 +501,40 @@ namespace DnsServerCore.Dns.ZoneManagers public override DnsDatagram QueryClosestDelegation(DnsDatagram request) { - _ = _root.FindZone(request.Question[0].Name, out _, out CacheZone delegation); - if (delegation is not null) + string domain = request.Question[0].Name; + + do { + _ = _root.FindZone(domain, out _, out CacheZone delegation); + if (delegation is null) + return null; + //return closest name servers in delegation IReadOnlyList closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, false, true); if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS) && (closestAuthority[0].Name.Length > 0)) //dont trust root name servers from cache! { if (request.DnssecOk) - closestAuthority = AddDSRecordsTo(delegation, false, closestAuthority); + { + if (closestAuthority[0].DnssecStatus != DnssecStatus.Disabled) //dont return records with disabled status + { + closestAuthority = AddDSRecordsTo(delegation, false, closestAuthority); - IReadOnlyList additional = GetAdditionalRecords(closestAuthority, false, request.DnssecOk); + IReadOnlyList additional = GetAdditionalRecords(closestAuthority, false, request.DnssecOk); - return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional); + return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional); + } + } + else + { + IReadOnlyList additional = GetAdditionalRecords(closestAuthority, false, request.DnssecOk); + + return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional); + } } + + domain = AuthZoneManager.GetParentZone(delegation.Name); } + while (domain is not null); //no cached delegation found return null; @@ -497,14 +561,23 @@ namespace DnsServerCore.Dns.ZoneManagers if (zone is not null) { //zone found - IReadOnlyList answers = zone.QueryRecords(question.Type, serveStaleAndResetExpiry, false); - if (answers.Count > 0) + IReadOnlyList answer = zone.QueryRecords(question.Type, serveStaleAndResetExpiry, false); + if (answer.Count > 0) { //answer found in cache - DnsResourceRecord firstRR = answers[0]; + DnsResourceRecord firstRR = answer[0]; if (firstRR.RDATA is DnsSpecialCacheRecord dnsSpecialCacheRecord) { + if (request.DnssecOk) + { + foreach (DnsResourceRecord originalAuthority in dnsSpecialCacheRecord.OriginalAuthority) + { + if (originalAuthority.DnssecStatus == DnssecStatus.Disabled) + goto beforeFindClosestNameServers; //dont return answer with disabled status + } + } + IReadOnlyList specialOptions = null; if (serveStaleAndResetExpiry) @@ -561,15 +634,15 @@ namespace DnsServerCore.Dns.ZoneManagers } } - DnsResourceRecord lastRR = answers[answers.Count - 1]; + DnsResourceRecord lastRR = answer[answer.Count - 1]; if ((lastRR.Type != question.Type) && (lastRR.Type == DnsResourceRecordType.CNAME) && (question.Type != DnsResourceRecordType.ANY)) { - List newAnswers = new List(answers.Count + 3); - newAnswers.AddRange(answers); + List newAnswers = new List(answer.Count + 3); + newAnswers.AddRange(answer); ResolveCNAME(question, lastRR, serveStaleAndResetExpiry, newAnswers); - answers = newAnswers; + answer = newAnswers; } IReadOnlyList authority = null; @@ -577,48 +650,16 @@ namespace DnsServerCore.Dns.ZoneManagers if (request.DnssecOk) { - //DNSSEC enabled; insert RRSIG records - List newAnswers = new List(answers.Count * 2); - List newAuthority = null; - - foreach (DnsResourceRecord answer in answers) + //DNSSEC enabled + foreach (DnsResourceRecord record in answer) { - newAnswers.Add(answer); - - DnsResourceRecordInfo rrInfo = answer.GetRecordInfo(); - - IReadOnlyList rrsigRecords = rrInfo.RRSIGRecords; - if (rrsigRecords is not null) - { - newAnswers.AddRange(rrsigRecords); - - foreach (DnsResourceRecord rrsigRecord in rrsigRecords) - { - if (!DnsRRSIGRecordData.IsWildcard(rrsigRecord)) - continue; - - //add NSEC/NSEC3 for the wildcard proof - if (newAuthority is null) - newAuthority = new List(2); - - IReadOnlyList nsecRecords = rrInfo.NSECRecords; - if (nsecRecords is not null) - { - foreach (DnsResourceRecord nsecRecord in nsecRecords) - { - newAuthority.Add(nsecRecord); - - IReadOnlyList nsecRRSIGRecords = nsecRecord.GetRecordInfo().RRSIGRecords; - if (nsecRRSIGRecords is not null) - newAuthority.AddRange(nsecRRSIGRecords); - } - } - } - } + if (record.DnssecStatus == DnssecStatus.Disabled) + goto beforeFindClosestNameServers; //dont return answer when status is disabled } - answers = newAnswers; - authority = newAuthority; + //add RRSIG records + AddRRSIGRecords(answer, out answer, out authority); + ednsFlags = EDnsHeaderFlags.DNSSEC_OK; } @@ -629,7 +670,7 @@ namespace DnsServerCore.Dns.ZoneManagers case DnsResourceRecordType.NS: case DnsResourceRecordType.MX: case DnsResourceRecordType.SRV: - additional = GetAdditionalRecords(answers, serveStaleAndResetExpiry, request.DnssecOk); + additional = GetAdditionalRecords(answer, serveStaleAndResetExpiry, request.DnssecOk); break; } @@ -637,7 +678,7 @@ namespace DnsServerCore.Dns.ZoneManagers if (serveStaleAndResetExpiry) { - foreach (DnsResourceRecord record in answers) + 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 draft-ietf-dnsop-serve-stale-04 @@ -655,7 +696,7 @@ namespace DnsServerCore.Dns.ZoneManagers options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOption(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; } - return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, answers[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, answers, authority, additional, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options); + return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, answer[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, answer, authority, additional, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options); } } else @@ -674,12 +715,45 @@ namespace DnsServerCore.Dns.ZoneManagers else rCode = DnsResponseCode.YXDomain; - return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, answer[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, rCode, request.Question, answer); + IReadOnlyList authority = null; + EDnsHeaderFlags ednsFlags = EDnsHeaderFlags.None; + + if (request.DnssecOk) + { + //DNSSEC enabled + foreach (DnsResourceRecord record in answer) + { + if (record.DnssecStatus == DnssecStatus.Disabled) + goto beforeFindClosestNameServers; //dont return answer when status is disabled + } + + //add RRSIG records + AddRRSIGRecords(answer, out answer, out authority); + + ednsFlags = EDnsHeaderFlags.DNSSEC_OK; + } + + EDnsOption[] options = null; + + if (serveStaleAndResetExpiry) + { + 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 draft-ietf-dnsop-serve-stale-04 + } + + options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOption(EDnsExtendedDnsErrorCode.StaleAnswer, null)) }; + } + + return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, answer[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, rCode, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options); } } } //no answer in cache + beforeFindClosestNameServers: + //check for closest delegation if any if (findClosestNameServers && (delegation is not null)) { @@ -696,15 +770,37 @@ namespace DnsServerCore.Dns.ZoneManagers return null; //no cached delegation found } - IReadOnlyList closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStaleAndResetExpiry, true); - if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS) && (closestAuthority[0].Name.Length > 0)) //dont trust root name servers from cache! + while (true) { - if (request.DnssecOk) - closestAuthority = AddDSRecordsTo(delegation, serveStaleAndResetExpiry, closestAuthority); + IReadOnlyList closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStaleAndResetExpiry, true); + if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS) && (closestAuthority[0].Name.Length > 0)) //dont trust root name servers from cache! + { + if (request.DnssecOk) + { + if (closestAuthority[0].DnssecStatus != DnssecStatus.Disabled) //dont return records with disabled status + { + closestAuthority = AddDSRecordsTo(delegation, serveStaleAndResetExpiry, closestAuthority); - IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStaleAndResetExpiry, request.DnssecOk); + IReadOnlyList additional = GetAdditionalRecords(closestAuthority, serveStaleAndResetExpiry, request.DnssecOk); - 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); + 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, request.DnssecOk); + + 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); + } + } + + string domain = AuthZoneManager.GetParentZone(delegation.Name); + if (domain is null) + return null; //dont find NS for root + + _ = _root.FindZone(domain, out _, out delegation); + if (delegation is null) + return null; //no cached delegation found } }