예제 #1
0
        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);
        }