public override DnsDatagram Query(DnsDatagram request, bool serveStaleAndResetExpiry = false) { DnsQuestionRecord question = request.Question[0]; CacheZone zone = _root.FindZone(question.Name, out CacheZone delegation, out _, out _); if (zone is null) { //zone not found if (serveStaleAndResetExpiry) { return(null); //recursive resolver does not make stale request so no need to return delegation response } if (delegation is not null) { //return closest name servers in delegation IReadOnlyList <DnsResourceRecord> 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! { IReadOnlyList <DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, serveStaleAndResetExpiry); return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional)); } } //no cached delegation found return(null); } //zone found IReadOnlyList <DnsResourceRecord> answers = zone.QueryRecords(question.Type, serveStaleAndResetExpiry, false); if (answers.Count > 0) { //answer found in cache DnsResourceRecord firstRR = answers[0]; if (firstRR.RDATA is DnsEmptyRecord dnsEmptyRecord) { if (serveStaleAndResetExpiry) { if (firstRR.IsStale) { firstRR.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per draft-ietf-dnsop-serve-stale-04 } if (dnsEmptyRecord.Authority is not null) { foreach (DnsResourceRecord record in dnsEmptyRecord.Authority) { 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 } } } } return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, dnsEmptyRecord.Authority)); } if (firstRR.RDATA is DnsNXRecord dnsNXRecord) { if (serveStaleAndResetExpiry) { if (firstRR.IsStale) { firstRR.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per draft-ietf-dnsop-serve-stale-04 } if (dnsNXRecord.Authority is not null) { foreach (DnsResourceRecord record in dnsNXRecord.Authority) { 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 } } } } return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NxDomain, request.Question, null, dnsNXRecord.Authority)); } if (firstRR.RDATA is DnsFailureRecord dnsFailureRecord) { if (serveStaleAndResetExpiry) { if (firstRR.IsStale) { firstRR.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per draft-ietf-dnsop-serve-stale-04 } } return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, dnsFailureRecord.RCODE, request.Question)); } DnsResourceRecord lastRR = answers[answers.Count - 1]; if ((lastRR.Type != question.Type) && (lastRR.Type == DnsResourceRecordType.CNAME) && (question.Type != DnsResourceRecordType.ANY)) { List <DnsResourceRecord> newAnswers = new List <DnsResourceRecord>(answers); ResolveCNAME(question, lastRR, serveStaleAndResetExpiry, newAnswers); answers = newAnswers; } IReadOnlyList <DnsResourceRecord> additional = null; switch (question.Type) { case DnsResourceRecordType.NS: case DnsResourceRecordType.MX: case DnsResourceRecordType.SRV: additional = GetAdditionalRecords(answers, serveStaleAndResetExpiry); break; } if (serveStaleAndResetExpiry) { foreach (DnsResourceRecord record in answers) { 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 } } if (additional is not null) { 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 draft-ietf-dnsop-serve-stale-04 } } } } return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, answers, null, additional)); } else { //no answer in cache if (serveStaleAndResetExpiry) { return(null); //recursive resolver does not make stale request so no need to return delegation response } //check for closest delegation if any if (delegation is not null) { //return closest name servers in delegation IReadOnlyList <DnsResourceRecord> 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! { IReadOnlyList <DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, false); return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional)); } } //no cached delegation found return(null); } }
public override DnsDatagram Query(DnsDatagram request, bool serveStaleAndResetExpiry = false, bool findClosestNameServers = false) { DnsQuestionRecord question = request.Question[0]; CacheZone zone; CacheZone closest = null; CacheZone delegation = null; if (findClosestNameServers) { zone = _root.FindZone(question.Name, out closest, out delegation); } else { if (!_root.TryGet(question.Name, out zone)) { _ = _root.FindZone(question.Name, out closest, out _); //zone not found; attempt to find closest } } if (zone is not null) { //zone found IReadOnlyList <DnsResourceRecord> answers = zone.QueryRecords(question.Type, serveStaleAndResetExpiry, false); if (answers.Count > 0) { //answer found in cache DnsResourceRecord firstRR = answers[0]; if (firstRR.RDATA is DnsSpecialCacheRecord dnsSpecialCacheRecord) { IReadOnlyList <EDnsOption> specialOptions = null; if (serveStaleAndResetExpiry) { if (firstRR.IsStale) { firstRR.ResetExpiry(30); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per draft-ietf-dnsop-serve-stale-04 } 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 draft-ietf-dnsop-serve-stale-04 } } } if (dnsSpecialCacheRecord.RCODE == DnsResponseCode.NxDomain) { List <EDnsOption> newOptions = new List <EDnsOption>(dnsSpecialCacheRecord.EDnsOptions.Count + 1); newOptions.AddRange(dnsSpecialCacheRecord.EDnsOptions); newOptions.Add(new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOption(EDnsExtendedDnsErrorCode.StaleNxDomainAnswer, null))); specialOptions = newOptions; } } if (specialOptions is null) { specialOptions = dnsSpecialCacheRecord.EDnsOptions; } if (request.DnssecOk) { bool authenticData; switch (dnsSpecialCacheRecord.Type) { case DnsSpecialCacheRecordType.NegativeCache: authenticData = true; break; default: authenticData = false; break; } if (request.CheckingDisabled) { return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, authenticData, request.CheckingDisabled, dnsSpecialCacheRecord.OriginalRCODE, request.Question, dnsSpecialCacheRecord.OriginalAnswer, dnsSpecialCacheRecord.OriginalAuthority, dnsSpecialCacheRecord.Additional, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, specialOptions)); } else { return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, authenticData, request.CheckingDisabled, dnsSpecialCacheRecord.RCODE, request.Question, null, dnsSpecialCacheRecord.Authority, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, specialOptions)); } } else { return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, dnsSpecialCacheRecord.RCODE, request.Question, null, dnsSpecialCacheRecord.NoDnssecAuthority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, specialOptions)); } } DnsResourceRecord lastRR = answers[answers.Count - 1]; if ((lastRR.Type != question.Type) && (lastRR.Type == DnsResourceRecordType.CNAME) && (question.Type != DnsResourceRecordType.ANY)) { List <DnsResourceRecord> newAnswers = new List <DnsResourceRecord>(answers.Count + 3); newAnswers.AddRange(answers); ResolveCNAME(question, lastRR, serveStaleAndResetExpiry, newAnswers); answers = newAnswers; } IReadOnlyList <DnsResourceRecord> authority = null; EDnsHeaderFlags ednsFlags = EDnsHeaderFlags.None; if (request.DnssecOk) { //DNSSEC enabled; insert RRSIG records List <DnsResourceRecord> newAnswers = new List <DnsResourceRecord>(answers.Count * 2); List <DnsResourceRecord> newAuthority = null; foreach (DnsResourceRecord answer in answers) { newAnswers.Add(answer); DnsResourceRecordInfo rrInfo = answer.GetRecordInfo(); IReadOnlyList <DnsResourceRecord> 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 <DnsResourceRecord>(2); } IReadOnlyList <DnsResourceRecord> nsecRecords = rrInfo.NSECRecords; if (nsecRecords is not null) { foreach (DnsResourceRecord nsecRecord in nsecRecords) { newAuthority.Add(nsecRecord); IReadOnlyList <DnsResourceRecord> nsecRRSIGRecords = nsecRecord.GetRecordInfo().RRSIGRecords; if (nsecRRSIGRecords is not null) { newAuthority.AddRange(nsecRRSIGRecords); } } } } } } answers = newAnswers; authority = newAuthority; ednsFlags = EDnsHeaderFlags.DNSSEC_OK; } IReadOnlyList <DnsResourceRecord> additional = null; switch (question.Type) { case DnsResourceRecordType.NS: case DnsResourceRecordType.MX: case DnsResourceRecordType.SRV: additional = GetAdditionalRecords(answers, serveStaleAndResetExpiry, request.DnssecOk); break; } EDnsOption[] options = null; if (serveStaleAndResetExpiry) { foreach (DnsResourceRecord record in answers) { 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 } } if (additional is not null) { 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 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, answers[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, answers, authority, additional, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options)); } } else { //zone not found //check for DNAME in closest zone if (closest is not null) { IReadOnlyList <DnsResourceRecord> answer = closest.QueryRecords(DnsResourceRecordType.DNAME, serveStaleAndResetExpiry, true); if ((answer.Count > 0) && (answer[0].Type == DnsResourceRecordType.DNAME)) { DnsResponseCode rCode; if (DoDNAMESubstitution(question, answer, serveStaleAndResetExpiry, out answer)) { rCode = DnsResponseCode.NoError; } 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)); } } } //no answer in cache //check for closest delegation if any if (findClosestNameServers && (delegation is not null)) { //return closest name servers in delegation if (question.Type == DnsResourceRecordType.DS) { //find parent delegation string domain = AuthZoneManager.GetParentZone(question.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 } } IReadOnlyList <DnsResourceRecord> 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) { closestAuthority = AddDSRecordsTo(delegation, serveStaleAndResetExpiry, closestAuthority); } IReadOnlyList <DnsResourceRecord> 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)); } } //no cached delegation found return(null); }