File: FallbackDnsResolver.cs
Web Access
Project: src\src\Microsoft.Extensions.ServiceDiscovery.Dns\Microsoft.Extensions.ServiceDiscovery.Dns.csproj (Microsoft.Extensions.ServiceDiscovery.Dns)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using DnsClient;
using DnsClient.Protocol;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
 
namespace Microsoft.Extensions.ServiceDiscovery.Dns;
 
internal sealed class FallbackDnsResolver : IDnsResolver
{
    private readonly LookupClient _lookupClient;
    private readonly IOptionsMonitor<DnsServiceEndpointProviderOptions> _options;
    private readonly TimeProvider _timeProvider;
 
    public FallbackDnsResolver(LookupClient lookupClient, IOptionsMonitor<DnsServiceEndpointProviderOptions> options, TimeProvider timeProvider)
    {
        _lookupClient = lookupClient;
        _options = options;
        _timeProvider = timeProvider;
    }
 
    private TimeSpan DefaultRefreshPeriod => _options.CurrentValue.DefaultRefreshPeriod;
 
    public async ValueTask<AddressResult[]> ResolveIPAddressesAsync(string name, CancellationToken cancellationToken = default)
    {
        DateTime expiresAt = _timeProvider.GetUtcNow().DateTime.Add(DefaultRefreshPeriod);
        var addresses = await System.Net.Dns.GetHostAddressesAsync(name, cancellationToken).ConfigureAwait(false);
 
        var results = new AddressResult[addresses.Length];
 
        for (int i = 0; i < addresses.Length; i++)
        {
            results[i] = new AddressResult
            {
                Address = addresses[i],
                ExpiresAt = expiresAt
            };
        }
 
        return results;
    }
 
    public async ValueTask<ServiceResult[]> ResolveServiceAsync(string name, CancellationToken cancellationToken = default)
    {
        DateTime now = _timeProvider.GetUtcNow().DateTime;
        var queryResult = await _lookupClient.QueryAsync(name, DnsClient.QueryType.SRV, cancellationToken: cancellationToken).ConfigureAwait(false);
        if (queryResult.HasError)
        {
            throw CreateException(name, queryResult.ErrorMessage);
        }
 
        var lookupMapping = new Dictionary<string, List<AddressResult>>();
        foreach (var record in queryResult.Additionals.OfType<AddressRecord>())
        {
            if (!lookupMapping.TryGetValue(record.DomainName, out var addresses))
            {
                addresses = new List<AddressResult>();
                lookupMapping[record.DomainName] = addresses;
            }
 
            addresses.Add(new AddressResult
            {
                Address = record.Address,
                ExpiresAt = now.Add(TimeSpan.FromSeconds(record.TimeToLive))
            });
        }
 
        var srvRecords = queryResult.Answers.OfType<SrvRecord>().ToList();
 
        var results = new ServiceResult[srvRecords.Count];
        for (int i = 0; i < srvRecords.Count; i++)
        {
            var record = srvRecords[i];
 
            results[i] = new ServiceResult
            {
                ExpiresAt = now.Add(TimeSpan.FromSeconds(record.TimeToLive)),
                Priority = record.Priority,
                Weight = record.Weight,
                Port = record.Port,
                Target = record.Target,
                Addresses = lookupMapping.TryGetValue(record.Target, out var addresses)
                    ? addresses.ToArray()
                    : Array.Empty<AddressResult>()
            };
        }
 
        return results;
    }
 
    private static InvalidOperationException CreateException(string dnsName, string errorMessage)
    {
        var msg = errorMessage switch
        {
            { Length: > 0 } => $"No DNS SRV records were found for DNS name '{dnsName}': {errorMessage}.",
            _ => $"No DNS SRV records were found for DNS name '{dnsName}'",
        };
        return new InvalidOperationException(msg);
    }
}