예제 #1
0
        protected override void CacheRecords(IReadOnlyList <DnsResourceRecord> resourceRecords)
        {
            if (resourceRecords.Count == 1)
            {
                CacheZone zone = _root.GetOrAdd(resourceRecords[0].Name, delegate(string key)
                {
                    return(new CacheZone(resourceRecords[0].Name));
                });

                zone.SetRecords(resourceRecords[0].Type, resourceRecords);
            }
            else
            {
                Dictionary <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);

                //add grouped records
                foreach (KeyValuePair <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByTypeRecords in groupedByDomainRecords)
                {
                    CacheZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate(string key)
                    {
                        return(new CacheZone(groupedByTypeRecords.Key));
                    });

                    foreach (KeyValuePair <DnsResourceRecordType, List <DnsResourceRecord> > groupedRecords in groupedByTypeRecords.Value)
                    {
                        zone.SetRecords(groupedRecords.Key, groupedRecords.Value);
                    }
                }
            }
        }
예제 #2
0
        private List <DnsResourceRecord> GetAdditionalRecords(IReadOnlyCollection <DnsResourceRecord> nsRecords, bool serveStale)
        {
            List <DnsResourceRecord> additionalRecords = new List <DnsResourceRecord>();

            foreach (DnsResourceRecord nsRecord in nsRecords)
            {
                if (nsRecord.Type != DnsResourceRecordType.NS)
                {
                    continue;
                }

                CacheZone cacheZone = _root.FindZone((nsRecord.RDATA as DnsNSRecord).NameServer, out _, out _, out _);
                if (cacheZone != null)
                {
                    {
                        IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.A, serveStale);
                        if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
                        {
                            additionalRecords.AddRange(records);
                        }
                    }

                    {
                        IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.AAAA, serveStale);
                        if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
                        {
                            additionalRecords.AddRange(records);
                        }
                    }
                }
            }

            return(additionalRecords);
        }
        private static IReadOnlyList <DnsResourceRecord> AddDSRecordsTo(CacheZone delegation, bool serveStale, IReadOnlyList <DnsResourceRecord> nsRecords)
        {
            IReadOnlyList <DnsResourceRecord> records = delegation.QueryRecords(DnsResourceRecordType.DS, serveStale, true);

            if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.DS))
            {
                List <DnsResourceRecord> newNSRecords = new List <DnsResourceRecord>(nsRecords.Count + records.Count);

                newNSRecords.AddRange(nsRecords);
                newNSRecords.AddRange(records);

                return(newNSRecords);
            }

            //no DS records found check for NSEC records
            IReadOnlyList <DnsResourceRecord> nsecRecords = nsRecords[0].GetRecordInfo().NSECRecords;

            if (nsecRecords is not null)
            {
                List <DnsResourceRecord> newNSRecords = new List <DnsResourceRecord>(nsRecords.Count + nsecRecords.Count);

                newNSRecords.AddRange(nsRecords);
                newNSRecords.AddRange(nsecRecords);

                return(newNSRecords);
            }

            //found nothing; return original NS records
            return(nsRecords);
        }
예제 #4
0
        protected override void CacheRecords(IReadOnlyList <DnsResourceRecord> resourceRecords)
        {
            //read and set glue records from base class
            foreach (DnsResourceRecord resourceRecord in resourceRecords)
            {
                IReadOnlyList <DnsResourceRecord> glueRecords = GetGlueRecordsFrom(resourceRecord);
                if (glueRecords.Count > 0)
                {
                    resourceRecord.SetGlueRecords(glueRecords);
                }
            }

            if (resourceRecords.Count == 1)
            {
                DnsResourceRecord resourceRecord = resourceRecords[0];

                if (resourceRecord.Name.Contains('*'))
                {
                    return;
                }

                CacheZone zone = _root.GetOrAdd(resourceRecord.Name, delegate(string key)
                {
                    return(new CacheZone(resourceRecord.Name, 1));
                });

                zone.SetRecords(resourceRecord.Type, resourceRecords, _dnsServer.ServeStale);
            }
            else
            {
                Dictionary <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);
                bool serveStale = _dnsServer.ServeStale;

                //add grouped records
                foreach (KeyValuePair <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByTypeRecords in groupedByDomainRecords)
                {
                    if (groupedByTypeRecords.Key.Contains('*'))
                    {
                        continue;
                    }

                    CacheZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate(string key)
                    {
                        return(new CacheZone(groupedByTypeRecords.Key, groupedByTypeRecords.Value.Count));
                    });

                    foreach (KeyValuePair <DnsResourceRecordType, List <DnsResourceRecord> > groupedRecords in groupedByTypeRecords.Value)
                    {
                        zone.SetRecords(groupedRecords.Key, groupedRecords.Value, serveStale);
                    }
                }
            }
        }
예제 #5
0
        private void ResolveAdditionalRecords(DnsResourceRecord refRecord, string domain, bool serveStale, List <DnsResourceRecord> additionalRecords)
        {
            IReadOnlyList <DnsResourceRecord> glueRecords = refRecord.GetGlueRecords();

            if (glueRecords.Count > 0)
            {
                bool added = false;

                foreach (DnsResourceRecord glueRecord in glueRecords)
                {
                    if (!glueRecord.IsStale)
                    {
                        added = true;
                        additionalRecords.Add(glueRecord);
                    }
                }

                if (added)
                {
                    return;
                }
            }

            CacheZone cacheZone = _root.FindZone(domain, out _, out _, out _);

            if (cacheZone != null)
            {
                {
                    IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.A, serveStale, true);
                    if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
                    {
                        additionalRecords.AddRange(records);
                    }
                }

                {
                    IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.AAAA, serveStale, true);
                    if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
                    {
                        additionalRecords.AddRange(records);
                    }
                }
            }
        }
예제 #6
0
        private void ResolveAdditionalRecords(string domain, bool serveStale, List <DnsResourceRecord> additionalRecords)
        {
            CacheZone cacheZone = _root.FindZone(domain, out _, out _, out _);

            if (cacheZone != null)
            {
                {
                    IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.A, serveStale);
                    if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
                    {
                        additionalRecords.AddRange(records);
                    }
                }

                {
                    IReadOnlyList <DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.AAAA, serveStale);
                    if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
                    {
                        additionalRecords.AddRange(records);
                    }
                }
            }
        }
예제 #7
0
        public override DnsDatagram Query(DnsDatagram request, bool serveStale = false)
        {
            CacheZone zone = _root.FindZone(request.Question[0].Name, out CacheZone delegation, out _, out _);

            if (zone == null)
            {
                //zone not found
                if (delegation == null)
                {
                    //no cached delegation found
                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question));
                }

                //return closest name servers in delegation
                IReadOnlyList <DnsResourceRecord> authority  = delegation.QueryRecords(DnsResourceRecordType.NS, serveStale, true);
                List <DnsResourceRecord>          additional = GetAdditionalRecords(authority, serveStale);

                return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional));
            }

            //zone found
            IReadOnlyList <DnsResourceRecord> answers = zone.QueryRecords(request.Question[0].Type, serveStale, false);

            if (answers.Count > 0)
            {
                //answer found in cache
                if (answers[0].RDATA is DnsEmptyRecord)
                {
                    DnsResourceRecord[] authority = null;
                    DnsResourceRecord   soaRecord = (answers[0].RDATA as DnsEmptyRecord).Authority;
                    if (soaRecord != null)
                    {
                        authority = new DnsResourceRecord[] { soaRecord }
                    }
                    ;

                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority));
                }

                if (answers[0].RDATA is DnsNXRecord)
                {
                    DnsResourceRecord[] authority = null;
                    DnsResourceRecord   soaRecord = (answers[0].RDATA as DnsNXRecord).Authority;
                    if (soaRecord != null)
                    {
                        authority = new DnsResourceRecord[] { soaRecord }
                    }
                    ;

                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NameError, request.Question, null, authority));
                }

                if (answers[0].RDATA is DnsANYRecord)
                {
                    DnsANYRecord anyRR = answers[0].RDATA as DnsANYRecord;
                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, anyRR.Records));
                }

                if (answers[0].RDATA is DnsFailureRecord)
                {
                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, (answers[0].RDATA as DnsFailureRecord).RCODE, request.Question));
                }

                IReadOnlyList <DnsResourceRecord> additional = null;

                switch (request.Question[0].Type)
                {
                case DnsResourceRecordType.NS:
                case DnsResourceRecordType.MX:
                case DnsResourceRecordType.SRV:
                    additional = GetAdditionalRecords(answers, serveStale);
                    break;
                }

                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; check for closest delegation if any
                if (delegation == null)
                {
                    //no cached delegation found
                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question));
                }

                //return closest name servers in delegation
                IReadOnlyList <DnsResourceRecord> authority  = delegation.QueryRecords(DnsResourceRecordType.NS, false, true);
                List <DnsResourceRecord>          additional = GetAdditionalRecords(authority, false);

                return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional));
            }
        }

        #endregion
    }
}
예제 #8
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);
            }
        }
예제 #9
0
        public override DnsDatagram Query(DnsDatagram request, bool serveStale = false)
        {
            DnsQuestionRecord question = request.Question[0];

            CacheZone zone = _root.FindZone(question.Name, out CacheZone delegation, out _, out _);

            if (zone == null)
            {
                //zone not found
                if (delegation != null)
                {
                    //return closest name servers in delegation
                    IReadOnlyList <DnsResourceRecord> closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStale, true);
                    if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS))
                    {
                        IReadOnlyList <DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, serveStale);

                        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(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question));
            }

            //zone found
            IReadOnlyList <DnsResourceRecord> answers = zone.QueryRecords(question.Type, serveStale, false);

            if (answers.Count > 0)
            {
                //answer found in cache
                DnsResourceRecord firstRR = answers[0];

                if (firstRR.RDATA is DnsEmptyRecord)
                {
                    DnsResourceRecord[] authority = null;
                    DnsResourceRecord   soaRecord = (firstRR.RDATA as DnsEmptyRecord).Authority;
                    if (soaRecord != null)
                    {
                        authority = new DnsResourceRecord[] { soaRecord }
                    }
                    ;

                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority));
                }

                if (firstRR.RDATA is DnsNXRecord)
                {
                    DnsResourceRecord[] authority = null;
                    DnsResourceRecord   soaRecord = (firstRR.RDATA as DnsNXRecord).Authority;
                    if (soaRecord != null)
                    {
                        authority = new DnsResourceRecord[] { soaRecord }
                    }
                    ;

                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NxDomain, request.Question, null, authority));
                }

                if (firstRR.RDATA is DnsFailureRecord)
                {
                    return(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, (firstRR.RDATA as 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, serveStale, newAnswers);

                    answers = newAnswers;
                }

                IReadOnlyList <DnsResourceRecord> additional = null;

                switch (question.Type)
                {
                case DnsResourceRecordType.NS:
                case DnsResourceRecordType.MX:
                case DnsResourceRecordType.SRV:
                    additional = GetAdditionalRecords(answers, serveStale);
                    break;
                }

                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; check for closest delegation if any
                if (delegation != 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))
                    {
                        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(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question));
            }
        }

        #endregion
    }
}
        protected override void CacheRecords(IReadOnlyList <DnsResourceRecord> resourceRecords)
        {
            List <DnsResourceRecord> dnameRecords = null;

            //read and set glue records from base class; also collect any DNAME records found
            foreach (DnsResourceRecord resourceRecord in resourceRecords)
            {
                IReadOnlyList <DnsResourceRecord> glueRecords  = GetGlueRecordsFrom(resourceRecord);
                IReadOnlyList <DnsResourceRecord> rrsigRecords = GetRRSIGRecordsFrom(resourceRecord);
                IReadOnlyList <DnsResourceRecord> nsecRecords  = GetNSECRecordsFrom(resourceRecord);

                if ((glueRecords is not null) || (rrsigRecords is not null) || (nsecRecords is not null))
                {
                    DnsResourceRecordInfo rrInfo = resourceRecord.GetRecordInfo();

                    rrInfo.GlueRecords  = glueRecords;
                    rrInfo.RRSIGRecords = rrsigRecords;
                    rrInfo.NSECRecords  = nsecRecords;

                    if (glueRecords is not null)
                    {
                        foreach (DnsResourceRecord glueRecord in glueRecords)
                        {
                            IReadOnlyList <DnsResourceRecord> glueRRSIGRecords = GetRRSIGRecordsFrom(glueRecord);
                            if (glueRRSIGRecords is not null)
                            {
                                glueRecord.GetRecordInfo().RRSIGRecords = glueRRSIGRecords;
                            }
                        }
                    }

                    if (nsecRecords is not null)
                    {
                        foreach (DnsResourceRecord nsecRecord in nsecRecords)
                        {
                            IReadOnlyList <DnsResourceRecord> nsecRRSIGRecords = GetRRSIGRecordsFrom(nsecRecord);
                            if (nsecRRSIGRecords is not null)
                            {
                                nsecRecord.GetRecordInfo().RRSIGRecords = nsecRRSIGRecords;
                            }
                        }
                    }
                }

                if (resourceRecord.Type == DnsResourceRecordType.DNAME)
                {
                    if (dnameRecords is null)
                    {
                        dnameRecords = new List <DnsResourceRecord>(1);
                    }

                    dnameRecords.Add(resourceRecord);
                }
            }

            if (resourceRecords.Count == 1)
            {
                DnsResourceRecord resourceRecord = resourceRecords[0];

                CacheZone zone = _root.GetOrAdd(resourceRecord.Name, delegate(string key)
                {
                    return(new CacheZone(resourceRecord.Name, 1));
                });

                if (zone.SetRecords(resourceRecord.Type, resourceRecords, _dnsServer.ServeStale))
                {
                    Interlocked.Increment(ref _totalEntries);
                }
            }
            else
            {
                Dictionary <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);
                bool serveStale = _dnsServer.ServeStale;

                int addedEntries = 0;

                //add grouped records
                foreach (KeyValuePair <string, Dictionary <DnsResourceRecordType, List <DnsResourceRecord> > > groupedByTypeRecords in groupedByDomainRecords)
                {
                    if (dnameRecords is not null)
                    {
                        bool foundSynthesizedCNAME = false;

                        foreach (DnsResourceRecord dnameRecord in dnameRecords)
                        {
                            if (groupedByTypeRecords.Key.EndsWith("." + dnameRecord.Name, StringComparison.OrdinalIgnoreCase))
                            {
                                foundSynthesizedCNAME = true;
                                break;
                            }
                        }

                        if (foundSynthesizedCNAME)
                        {
                            continue; //do not cache synthesized CNAME
                        }
                    }

                    CacheZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate(string key)
                    {
                        return(new CacheZone(groupedByTypeRecords.Key, groupedByTypeRecords.Value.Count));
                    });

                    foreach (KeyValuePair <DnsResourceRecordType, List <DnsResourceRecord> > groupedRecords in groupedByTypeRecords.Value)
                    {
                        if (zone.SetRecords(groupedRecords.Key, groupedRecords.Value, serveStale))
                        {
                            addedEntries++;
                        }
                    }
                }

                if (addedEntries > 0)
                {
                    Interlocked.Add(ref _totalEntries, addedEntries);
                }
            }
        }
        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);
        }