/// <summary>
        ///   Queries a the upstream DNS server(s) for specified records.
        /// </summary>
        /// <typeparam name="T"> Type of records, that should be returned </typeparam>
        /// <param name="name"> Domain, that should be queried </param>
        /// <param name="recordType"> Type the should be queried </param>
        /// <param name="recordClass"> Class the should be queried </param>
        /// <returns> A list of matching <see cref="DnsRecordBase">records</see> </returns>
        public List <T> Resolve <T>(DomainName name, RecordType recordType = RecordType.A, RecordClass recordClass = RecordClass.INet)
            where T : DnsRecordBase
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name), "Name must be provided");
            }

            List <T> records;

            if (_cache.TryGetRecords(name, recordType, recordClass, out records))
            {
                return(records);
            }

            DnsMessage msg = _dnsClient.Resolve(name, recordType, recordClass);

            if ((msg == null) || ((msg.ReturnCode != ReturnCode.NoError) && (msg.ReturnCode != ReturnCode.NxDomain)))
            {
                throw new Exception("DNS request failed");
            }

            CNameRecord cName = msg.AnswerRecords.Where(x => (x.RecordType == RecordType.CName) && (x.RecordClass == recordClass) && x.Name.Equals(name)).OfType <CNameRecord>().FirstOrDefault();

            if (cName != null)
            {
                records = msg.AnswerRecords.Where(x => x.Name.Equals(cName.CanonicalName)).OfType <T>().ToList();
                if (records.Count > 0)
                {
                    _cache.Add(name, recordType, recordClass, records, DnsSecValidationResult.Indeterminate, records.Min(x => x.TimeToLive));
                    return(records);
                }

                records = Resolve <T>(cName.CanonicalName, recordType, recordClass);

                if (records.Count > 0)
                {
                    _cache.Add(name, recordType, recordClass, records, DnsSecValidationResult.Indeterminate, records.Min(x => x.TimeToLive));
                }

                return(records);
            }

            records = msg.AnswerRecords.Where(x => x.Name.Equals(name)).OfType <T>().ToList();

            if (records.Count > 0)
            {
                _cache.Add(name, recordType, recordClass, records, DnsSecValidationResult.Indeterminate, records.Min(x => x.TimeToLive));
            }

            return(records);
        }
        private async Task <List <T> > ResolveAsyncInternal <T>(DomainName name, RecordType recordType, RecordClass recordClass, State state, CancellationToken token)
            where T : DnsRecordBase
        {
            List <T> cachedResults;

            if (_cache.TryGetRecords(name, recordType, recordClass, out cachedResults))
            {
                return(cachedResults);
            }

            List <CNameRecord> cachedCNames;

            if (_cache.TryGetRecords(name, RecordType.CName, recordClass, out cachedCNames))
            {
                return(await ResolveAsyncInternal <T>(cachedCNames.First().CanonicalName, recordType, recordClass, state, token));
            }

            DnsMessage msg = await ResolveMessageAsync(name, recordType, recordClass, state, token);

            // check for cname
            List <DnsRecordBase> cNameRecords = msg.AnswerRecords.Where(x => (x.RecordType == RecordType.CName) && (x.RecordClass == recordClass) && x.Name.Equals(name)).ToList();

            if (cNameRecords.Count > 0)
            {
                _cache.Add(name, RecordType.CName, recordClass, cNameRecords, DnsSecValidationResult.Indeterminate, cNameRecords.Min(x => x.TimeToLive));

                DomainName canonicalName = ((CNameRecord)cNameRecords.First()).CanonicalName;

                List <DnsRecordBase> matchingAdditionalRecords = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(canonicalName)).ToList();
                if (matchingAdditionalRecords.Count > 0)
                {
                    _cache.Add(canonicalName, recordType, recordClass, matchingAdditionalRecords, DnsSecValidationResult.Indeterminate, matchingAdditionalRecords.Min(x => x.TimeToLive));
                    return(matchingAdditionalRecords.OfType <T>().ToList());
                }

                return(await ResolveAsyncInternal <T>(canonicalName, recordType, recordClass, state, token));
            }

            // check for "normal" answer
            List <DnsRecordBase> answerRecords = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(name)).ToList();

            if (answerRecords.Count > 0)
            {
                _cache.Add(name, recordType, recordClass, answerRecords, DnsSecValidationResult.Indeterminate, answerRecords.Min(x => x.TimeToLive));
                return(answerRecords.OfType <T>().ToList());
            }

            // check for negative answer
            SoaRecord soaRecord = msg.AuthorityRecords
                                  .Where(x =>
                                         (x.RecordType == RecordType.Soa) &&
                                         (name.Equals(x.Name) || name.IsSubDomainOf(x.Name)))
                                  .OfType <SoaRecord>()
                                  .FirstOrDefault();

            if (soaRecord != null)
            {
                _cache.Add(name, recordType, recordClass, new List <DnsRecordBase>(), DnsSecValidationResult.Indeterminate, soaRecord.NegativeCachingTTL);
                return(new List <T>());
            }

            // authoritive response does not contain answer
            throw new Exception("Could not resolve " + name);
        }
Пример #3
0
        private async Task <DnsSecResult <T> > ResolveAsyncInternal <T>(DomainName name, RecordType recordType, RecordClass recordClass, State state, CancellationToken token)
            where T : DnsRecordBase
        {
            DnsCacheRecordList <T> cachedResults;

            if (_cache.TryGetRecords(name, recordType, recordClass, out cachedResults))
            {
                return(new DnsSecResult <T>(cachedResults, cachedResults.ValidationResult));
            }

            DnsCacheRecordList <CNameRecord> cachedCNames;

            if (_cache.TryGetRecords(name, RecordType.CName, recordClass, out cachedCNames))
            {
                var cNameResult = await ResolveAsyncInternal <T>(cachedCNames.First().CanonicalName, recordType, recordClass, state, token);

                return(new DnsSecResult <T>(cNameResult.Records, cachedCNames.ValidationResult == cNameResult.ValidationResult ? cachedCNames.ValidationResult : DnsSecValidationResult.Unsigned));
            }

            DnsMessage msg = await ResolveMessageAsync(name, recordType, recordClass, state, token);

            // check for cname
            List <DnsRecordBase> cNameRecords = msg.AnswerRecords.Where(x => (x.RecordType == RecordType.CName) && (x.RecordClass == recordClass) && x.Name.Equals(name)).ToList();

            if (cNameRecords.Count > 0)
            {
                DnsSecValidationResult cNameValidationResult = await _validator.ValidateAsync(name, RecordType.CName, recordClass, msg, cNameRecords, state, token);

                if ((cNameValidationResult == DnsSecValidationResult.Bogus) || (cNameValidationResult == DnsSecValidationResult.Indeterminate))
                {
                    throw new DnsSecValidationException("CNAME record could not be validated");
                }

                _cache.Add(name, RecordType.CName, recordClass, cNameRecords, cNameValidationResult, cNameRecords.Min(x => x.TimeToLive));

                DomainName canonicalName = ((CNameRecord)cNameRecords.First()).CanonicalName;

                List <DnsRecordBase> matchingAdditionalRecords = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(canonicalName)).ToList();
                if (matchingAdditionalRecords.Count > 0)
                {
                    DnsSecValidationResult matchingValidationResult = await _validator.ValidateAsync(canonicalName, recordType, recordClass, msg, matchingAdditionalRecords, state, token);

                    if ((matchingValidationResult == DnsSecValidationResult.Bogus) || (matchingValidationResult == DnsSecValidationResult.Indeterminate))
                    {
                        throw new DnsSecValidationException("CNAME matching records could not be validated");
                    }

                    DnsSecValidationResult validationResult = cNameValidationResult == matchingValidationResult ? cNameValidationResult : DnsSecValidationResult.Unsigned;
                    _cache.Add(canonicalName, recordType, recordClass, matchingAdditionalRecords, validationResult, matchingAdditionalRecords.Min(x => x.TimeToLive));

                    return(new DnsSecResult <T>(matchingAdditionalRecords.OfType <T>().ToList(), validationResult));
                }

                var cNameResults = await ResolveAsyncInternal <T>(canonicalName, recordType, recordClass, state, token);

                return(new DnsSecResult <T>(cNameResults.Records, cNameValidationResult == cNameResults.ValidationResult ? cNameValidationResult : DnsSecValidationResult.Unsigned));
            }

            // check for "normal" answer
            List <DnsRecordBase> answerRecords = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(name)).ToList();

            if (answerRecords.Count > 0)
            {
                DnsSecValidationResult validationResult = await _validator.ValidateAsync(name, recordType, recordClass, msg, answerRecords, state, token);

                if ((validationResult == DnsSecValidationResult.Bogus) || (validationResult == DnsSecValidationResult.Indeterminate))
                {
                    throw new DnsSecValidationException("Response records could not be validated");
                }

                _cache.Add(name, recordType, recordClass, answerRecords, validationResult, answerRecords.Min(x => x.TimeToLive));
                return(new DnsSecResult <T>(answerRecords.OfType <T>().ToList(), validationResult));
            }

            // check for negative answer
            SoaRecord soaRecord = msg.AuthorityRecords
                                  .Where(x =>
                                         (x.RecordType == RecordType.Soa) &&
                                         (name.Equals(x.Name) || name.IsSubDomainOf(x.Name)))
                                  .OfType <SoaRecord>()
                                  .FirstOrDefault();

            if (soaRecord != null)
            {
                DnsSecValidationResult validationResult = await _validator.ValidateAsync(name, recordType, recordClass, msg, answerRecords, state, token);

                if ((validationResult == DnsSecValidationResult.Bogus) || (validationResult == DnsSecValidationResult.Indeterminate))
                {
                    throw new DnsSecValidationException("Negative answer could not be validated");
                }

                _cache.Add(name, recordType, recordClass, new List <DnsRecordBase>(), validationResult, soaRecord.NegativeCachingTTL);
                return(new DnsSecResult <T>(new List <T>(), validationResult));
            }

            // authoritive response does not contain answer
            throw new Exception("Could not resolve " + name);
        }
Пример #4
0
        /// <summary>
        ///   Resolves specified records as an asynchronous operation.
        /// </summary>
        /// <typeparam name="T"> Type of records, that should be returned </typeparam>
        /// <param name="name"> Domain, that should be queried </param>
        /// <param name="recordType"> Type the should be queried </param>
        /// <param name="recordClass"> Class the should be queried </param>
        /// <param name="token"> The token to monitor cancellation requests </param>
        /// <returns> A list of matching <see cref="DnsRecordBase">records</see> </returns>
        public async Task <DnsSecResult <T> > ResolveSecureAsync <T>(DomainName name, RecordType recordType = RecordType.A, RecordClass recordClass = RecordClass.INet, CancellationToken token = default(CancellationToken))
            where T : DnsRecordBase
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name), "Name must be provided");
            }

            DnsCacheRecordList <T> cacheResult;

            if (_cache.TryGetRecords(name, recordType, recordClass, out cacheResult))
            {
                return(new DnsSecResult <T>(cacheResult, cacheResult.ValidationResult));
            }

            DnsMessage msg = await _dnsClient.ResolveAsync(name, recordType, recordClass, new DnsQueryOptions()
            {
                IsEDnsEnabled      = true,
                IsDnsSecOk         = true,
                IsCheckingDisabled = true,
                IsRecursionDesired = true
            }, token).ConfigureAwait(false);

            if ((msg == null) || ((msg.ReturnCode != ReturnCode.NoError) && (msg.ReturnCode != ReturnCode.NxDomain)))
            {
                throw new Exception("DNS request failed");
            }

            DnsSecValidationResult validationResult;

            CNameRecord cName = msg.AnswerRecords.Where(x => (x.RecordType == RecordType.CName) && (x.RecordClass == recordClass) && x.Name.Equals(name)).OfType <CNameRecord>().FirstOrDefault();

            if (cName != null)
            {
                DnsSecValidationResult cNameValidationResult = await _validator.ValidateAsync(name, RecordType.CName, recordClass, msg, new List <CNameRecord>() { cName }, null, token).ConfigureAwait(false);

                if ((cNameValidationResult == DnsSecValidationResult.Bogus) || (cNameValidationResult == DnsSecValidationResult.Indeterminate))
                {
                    throw new DnsSecValidationException("CNAME record could not be validated");
                }

                var records = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(cName.CanonicalName)).OfType <T>().ToList();
                if (records.Count > 0)
                {
                    DnsSecValidationResult recordsValidationResult = await _validator.ValidateAsync(cName.CanonicalName, recordType, recordClass, msg, records, null, token).ConfigureAwait(false);

                    if ((recordsValidationResult == DnsSecValidationResult.Bogus) || (recordsValidationResult == DnsSecValidationResult.Indeterminate))
                    {
                        throw new DnsSecValidationException("CNAME matching records could not be validated");
                    }

                    validationResult = cNameValidationResult == recordsValidationResult ? cNameValidationResult : DnsSecValidationResult.Unsigned;
                    _cache.Add(name, recordType, recordClass, records, validationResult, Math.Min(cName.TimeToLive, records.Min(x => x.TimeToLive)));

                    return(new DnsSecResult <T>(records, validationResult));
                }

                var cNameResults = await ResolveSecureAsync <T>(cName.CanonicalName, recordType, recordClass, token).ConfigureAwait(false);

                validationResult = cNameValidationResult == cNameResults.ValidationResult ? cNameValidationResult : DnsSecValidationResult.Unsigned;

                if (cNameResults.Records.Count > 0)
                {
                    _cache.Add(name, recordType, recordClass, cNameResults.Records, validationResult, Math.Min(cName.TimeToLive, cNameResults.Records.Min(x => x.TimeToLive)));
                }

                return(new DnsSecResult <T>(cNameResults.Records, validationResult));
            }

            List <T> res = msg.AnswerRecords.Where(x => (x.RecordType == recordType) && (x.RecordClass == recordClass) && x.Name.Equals(name)).OfType <T>().ToList();

            validationResult = await _validator.ValidateAsync(name, recordType, recordClass, msg, res, null, token).ConfigureAwait(false);

            if ((validationResult == DnsSecValidationResult.Bogus) || (validationResult == DnsSecValidationResult.Indeterminate))
            {
                throw new DnsSecValidationException("Response records could not be validated");
            }

            if (res.Count > 0)
            {
                _cache.Add(name, recordType, recordClass, res, validationResult, res.Min(x => x.TimeToLive));
            }

            return(new DnsSecResult <T>(res, validationResult));
        }