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); }
/// <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)); }