From d3371361d3fed89ac1960966aabb0dd1df20004a Mon Sep 17 00:00:00 2001 From: Shreyas Zare Date: Sat, 16 Jul 2022 12:38:04 +0530 Subject: [PATCH] CacheZoneManager: Added AddRRSIGRecords() to allow reusing the code. Updated QueryClosestDelegation() and Query() to return only validated records when request has DO flag set. Updated Query() to fix issue that failed to return RRSIG when returning DNAME. --- .../Dns/ZoneManagers/CacheZoneManager.cs | 218 +++++++++++++----- 1 file changed, 157 insertions(+), 61 deletions(-) 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 } }