Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2c217a3
First integration of Resolver sources
rzikm Oct 4, 2024
704e33c
Allow mocking
rzikm Oct 4, 2024
aff6d17
Fix dispose
rzikm Oct 4, 2024
15db8a1
Fix tests
rzikm Oct 4, 2024
6bda235
Actually run ResolvConf tests
rzikm Oct 4, 2024
6fc61e0
Add retry functionality
rzikm Oct 8, 2024
2b6d91e
Add logging
rzikm Oct 10, 2024
2137954
Fix Microsoft.Extensions.ServiceDiscovery.Yarp.Tests
rzikm Oct 10, 2024
c204b01
Merge remote-tracking branch 'upstream/main' into managed-resolver
rzikm Nov 26, 2024
2fc7de4
Activity WIP
rzikm Nov 26, 2024
9b4de29
Fix reading CNAME with pointers in domain name segments
rzikm Nov 28, 2024
3ad5a75
Reenable telemetry
rzikm Nov 28, 2024
40f8f9b
Last changes to telemetry
rzikm Nov 28, 2024
e385605
Use strong RNG for Transaction ID generation
rzikm Jan 15, 2025
3cf0a9d
Check against too long domain name
rzikm Feb 3, 2025
6016e41
Disallow pointer to pointer.
rzikm Feb 3, 2025
f8c189d
Code review feedback (easy fixes)
rzikm Feb 3, 2025
d929889
More code review feedback
rzikm Feb 6, 2025
00e41d7
Fix increment
rzikm Feb 6, 2025
7267fb7
Detect CNAME loops
rzikm Feb 7, 2025
c1b76d3
Handle empty Tcp fallback responses and lack of TCP failover support …
rzikm Feb 7, 2025
86490cc
Dispose result.Response when overwritten by another result.
rzikm Feb 7, 2025
92b2905
Move DnsMessageHeader parsing to DnsPrimitives
rzikm Feb 11, 2025
fdf1904
Rework and add tests to retries and failover
rzikm Feb 12, 2025
61814e7
Test failover after exhausting attempts on one server
rzikm Feb 13, 2025
82a8108
Streamline options, remove unsupported options from ResolverOptions
rzikm Feb 13, 2025
fa627fd
Better handling of malformed responses
rzikm Feb 13, 2025
4ec79ca
Code review feedback
rzikm Mar 25, 2025
123375d
More feedback
rzikm Mar 27, 2025
6311616
Guarantee linear parsing of CNAME chains
rzikm Mar 27, 2025
c57a8c5
Fix decoding compressed CNAME in TCP fallback
rzikm Mar 27, 2025
83ca958
More code coverage
rzikm May 12, 2025
03e6a99
Correctly handle IDN names
rzikm May 15, 2025
46418fe
Minor fixes
rzikm May 22, 2025
4075e4f
Add Fuzzing tests for DNS resolver
rzikm May 23, 2025
56cd169
Improve fuzzing of EncodedDomainName
rzikm May 23, 2025
a19e8a2
Merge remote-tracking branch 'upstream/main' into managed-resolver
rzikm Jun 10, 2025
5d5edae
Remove commented out code
rzikm Jun 10, 2025
3c387e9
Fix build
rzikm Jun 10, 2025
c6e3d63
Fix Yarp service discovery tests
rzikm Jun 10, 2025
e66229a
Lazy TCP socket allocation in LoopbackTests
rzikm Jun 12, 2025
da018f7
LoopbackTestBaseLogging
rzikm Jun 12, 2025
2697412
fixup! LoopbackTestBaseLogging
rzikm Jun 12, 2025
214cc9b
Downgrade SharpFuzz
rzikm Jun 17, 2025
0d891cb
Fix buffer use after returning to pool
rzikm Jun 18, 2025
46cf262
Merge branch 'main' into managed-resolver
rzikm Jun 18, 2025
35a5809
Fix build
rzikm Jun 18, 2025
fcf18cf
Merge branch 'main' into managed-resolver
rzikm Jun 25, 2025
df785d1
Remove DnsClient package ref
rzikm Jun 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
<!-- external dependencies -->
<PackageVersion Include="Confluent.Kafka" Version="2.10.1" />
<PackageVersion Include="Dapper" Version="2.1.66" />
<PackageVersion Include="DnsClient" Version="1.8.0" />
<PackageVersion Include="Google.Protobuf" Version="3.31.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.71.0" />
Expand Down Expand Up @@ -126,6 +125,8 @@
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Archives" Version="$(MicrosoftDotNetBuildTasksArchivesVersion)" />
<PackageVersion Include="Microsoft.DotNet.GenAPI.Task" Version="9.0.103-servicing.25065.25" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
<!-- Fuzzing tests dependencies -->
<PackageVersion Include="SharpFuzz" Version="$(SharpFuzzPackageVersion)" />
<!-- playground apps dependencies -->
<PackageVersion Include="Microsoft.Orleans.Clustering.AzureStorage" Version="9.1.2" />
<PackageVersion Include="Microsoft.Orleans.Persistence.AzureStorage" Version="9.1.2" />
Expand Down
2 changes: 2 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
<!-- for templates -->
<MicrosoftAspNetCorePackageVersionForNet9>9.0.6</MicrosoftAspNetCorePackageVersionForNet9>
<MicrosoftAspNetCorePackageVersionForNet10>10.0.0-preview.5.25277.114</MicrosoftAspNetCorePackageVersionForNet10>
<!-- Fuzzing tests -->
<SharpFuzzPackageVersion>2.1.1</SharpFuzzPackageVersion>
<!-- Aspire.Cli uses a preview version of StreamJsonRpc which is needed for native AOT support. -->
<StreamJsonRpcPackageVersionForCli>2.23.32-alpha</StreamJsonRpcPackageVersionForCli>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;

namespace Microsoft.Extensions.ServiceDiscovery.Dns;

Expand All @@ -12,6 +13,7 @@ internal sealed partial class DnsServiceEndpointProvider(
string hostName,
IOptionsMonitor<DnsServiceEndpointProviderOptions> options,
ILogger<DnsServiceEndpointProvider> logger,
IDnsResolver resolver,
TimeProvider timeProvider) : DnsServiceEndpointProviderBase(query, logger, timeProvider), IHostNameFeature
{
protected override double RetryBackOffFactor => options.CurrentValue.RetryBackOffFactor;
Expand All @@ -29,17 +31,14 @@ protected override async Task ResolveAsyncCore()
var endpoints = new List<ServiceEndpoint>();
var ttl = DefaultRefreshPeriod;
Log.AddressQuery(logger, ServiceName, hostName);
var addresses = await System.Net.Dns.GetHostAddressesAsync(hostName, ShutdownToken).ConfigureAwait(false);

var now = _timeProvider.GetUtcNow().DateTime;
var addresses = await resolver.ResolveIPAddressesAsync(hostName, ShutdownToken).ConfigureAwait(false);

foreach (var address in addresses)
{
var serviceEndpoint = ServiceEndpoint.Create(new IPEndPoint(address, 0));
serviceEndpoint.Features.Set<IServiceEndpointProvider>(this);
if (options.CurrentValue.ShouldApplyHostNameMetadata(serviceEndpoint))
{
serviceEndpoint.Features.Set<IHostNameFeature>(this);
}

endpoints.Add(serviceEndpoint);
ttl = MinTtl(now, address.ExpiresAt, ttl);
endpoints.Add(CreateEndpoint(new IPEndPoint(address.Address, port: 0)));
}

if (endpoints.Count == 0)
Expand All @@ -48,5 +47,23 @@ protected override async Task ResolveAsyncCore()
}

SetResult(endpoints, ttl);

static TimeSpan MinTtl(DateTime now, DateTime expiresAt, TimeSpan existing)
{
var candidate = expiresAt - now;
return candidate < existing ? candidate : existing;
}

ServiceEndpoint CreateEndpoint(EndPoint endPoint)
{
var serviceEndpoint = ServiceEndpoint.Create(endPoint);
serviceEndpoint.Features.Set<IServiceEndpointProvider>(this);
if (options.CurrentValue.ShouldApplyHostNameMetadata(serviceEndpoint))
{
serviceEndpoint.Features.Set<IHostNameFeature>(this);
}

return serviceEndpoint;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal abstract partial class DnsServiceEndpointProviderBase : IServiceEndpoin
private readonly object _lock = new();
private readonly ILogger _logger;
private readonly CancellationTokenSource _disposeCancellation = new();
private readonly TimeProvider _timeProvider;
protected readonly TimeProvider _timeProvider;
private long _lastRefreshTimeStamp;
private Task _resolveTask = Task.CompletedTask;
private bool _hasEndpoints;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;

namespace Microsoft.Extensions.ServiceDiscovery.Dns;

internal sealed partial class DnsServiceEndpointProviderFactory(
IOptionsMonitor<DnsServiceEndpointProviderOptions> options,
ILogger<DnsServiceEndpointProvider> logger,
IDnsResolver resolver,
TimeProvider timeProvider) : IServiceEndpointProviderFactory
{
/// <inheritdoc/>
public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] out IServiceEndpointProvider? provider)
{
provider = new DnsServiceEndpointProvider(query, hostName: query.ServiceName, options, logger, timeProvider);
provider = new DnsServiceEndpointProvider(query, hostName: query.ServiceName, options, logger, resolver, timeProvider);
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using DnsClient;
using DnsClient.Protocol;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;

namespace Microsoft.Extensions.ServiceDiscovery.Dns;

Expand All @@ -15,7 +14,7 @@ internal sealed partial class DnsSrvServiceEndpointProvider(
string hostName,
IOptionsMonitor<DnsSrvServiceEndpointProviderOptions> options,
ILogger<DnsSrvServiceEndpointProvider> logger,
IDnsQuery dnsClient,
IDnsResolver resolver,
TimeProvider timeProvider) : DnsServiceEndpointProviderBase(query, logger, timeProvider), IHostNameFeature
{
protected override double RetryBackOffFactor => options.CurrentValue.RetryBackOffFactor;
Expand All @@ -35,56 +34,36 @@ protected override async Task ResolveAsyncCore()
var endpoints = new List<ServiceEndpoint>();
var ttl = DefaultRefreshPeriod;
Log.SrvQuery(logger, ServiceName, srvQuery);
var result = await dnsClient.QueryAsync(srvQuery, QueryType.SRV, cancellationToken: ShutdownToken).ConfigureAwait(false);
if (result.HasError)
{
throw CreateException(srvQuery, result.ErrorMessage);
}

var lookupMapping = new Dictionary<string, DnsResourceRecord>();
foreach (var record in result.Additionals.Where(x => x is AddressRecord or CNameRecord))
{
ttl = MinTtl(record, ttl);
lookupMapping[record.DomainName] = record;
}
var now = _timeProvider.GetUtcNow().DateTime;
var result = await resolver.ResolveServiceAsync(srvQuery, cancellationToken: ShutdownToken).ConfigureAwait(false);

var srvRecords = result.Answers.OfType<SrvRecord>();
foreach (var record in srvRecords)
foreach (var record in result)
{
if (!lookupMapping.TryGetValue(record.Target, out var targetRecord))
{
continue;
}
ttl = MinTtl(now, record.ExpiresAt, ttl);

ttl = MinTtl(record, ttl);
if (targetRecord is AddressRecord addressRecord)
if (record.Addresses.Length > 0)
{
endpoints.Add(CreateEndpoint(new IPEndPoint(addressRecord.Address, record.Port)));
foreach (var address in record.Addresses)
{
ttl = MinTtl(now, address.ExpiresAt, ttl);
endpoints.Add(CreateEndpoint(new IPEndPoint(address.Address, record.Port)));
}
}
else if (targetRecord is CNameRecord canonicalNameRecord)
else
{
endpoints.Add(CreateEndpoint(new DnsEndPoint(canonicalNameRecord.CanonicalName.Value.TrimEnd('.'), record.Port)));
endpoints.Add(CreateEndpoint(new DnsEndPoint(record.Target.TrimEnd('.'), record.Port)));
}
}

SetResult(endpoints, ttl);

static TimeSpan MinTtl(DnsResourceRecord record, TimeSpan existing)
static TimeSpan MinTtl(DateTime now, DateTime expiresAt, TimeSpan existing)
{
var candidate = TimeSpan.FromSeconds(record.TimeToLive);
var candidate = expiresAt - now;
return candidate < existing ? candidate : existing;
}

InvalidOperationException CreateException(string dnsName, string errorMessage)
{
var msg = errorMessage switch
{
{ Length: > 0 } => $"No DNS records were found for service '{ServiceName}' (DNS name: '{dnsName}'): {errorMessage}.",
_ => $"No DNS records were found for service '{ServiceName}' (DNS name: '{dnsName}')."
};
return new InvalidOperationException(msg);
}

ServiceEndpoint CreateEndpoint(EndPoint endPoint)
{
var serviceEndpoint = ServiceEndpoint.Create(endPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using DnsClient;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;

namespace Microsoft.Extensions.ServiceDiscovery.Dns;

internal sealed partial class DnsSrvServiceEndpointProviderFactory(
IOptionsMonitor<DnsSrvServiceEndpointProviderOptions> options,
ILogger<DnsSrvServiceEndpointProvider> logger,
IDnsQuery dnsClient,
IDnsResolver resolver,
TimeProvider timeProvider) : IServiceEndpointProviderFactory
{
private static readonly string s_serviceAccountPath = Path.Combine($"{Path.DirectorySeparatorChar}var", "run", "secrets", "kubernetes.io", "serviceaccount");
private static readonly string s_serviceAccountNamespacePath = Path.Combine($"{Path.DirectorySeparatorChar}var", "run", "secrets", "kubernetes.io", "serviceaccount", "namespace");
private static readonly string s_resolveConfPath = Path.Combine($"{Path.DirectorySeparatorChar}etc", "resolv.conf");
private readonly string? _querySuffix = options.CurrentValue.QuerySuffix ?? GetKubernetesHostDomain();
private readonly string? _querySuffix = options.CurrentValue.QuerySuffix?.TrimStart('.') ?? GetKubernetesHostDomain();

/// <inheritdoc/>
public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] out IServiceEndpointProvider? provider)
{
// If a default namespace is not specified, then this provider will attempt to infer the namespace from the service name, but only when running inside Kubernetes.
// Kubernetes DNS spec: https://github.com/kubernetes/dns/blob/master/docs/specification.md
// SRV records are available for headless services with named ports.
// SRV records are available for headless services with named ports.
// They take the form $"_{portName}._{protocol}.{serviceName}.{namespace}.{suffix}"
// The suffix (after the service name) can be parsed from /etc/resolv.conf
// Otherwise, the namespace can be read from /var/run/secrets/kubernetes.io/serviceaccount/namespace and combined with an assumed suffix of "svc.cluster.local".
Expand All @@ -39,7 +39,7 @@ public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] ou

var portName = query.EndpointName ?? "default";
var srvQuery = $"_{portName}._tcp.{query.ServiceName}.{_querySuffix}";
provider = new DnsSrvServiceEndpointProvider(query, srvQuery, hostName: query.ServiceName, options, logger, dnsClient, timeProvider);
provider = new DnsSrvServiceEndpointProvider(query, srvQuery, hostName: query.ServiceName, options, logger, resolver, timeProvider);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DnsClient" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
Expand All @@ -23,7 +22,9 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Extensions.ServiceDiscovery.Dns.Tests"/>
<InternalsVisibleTo Include="Microsoft.Extensions.ServiceDiscovery.Dns.Tests" />
<InternalsVisibleTo Include="Microsoft.Extensions.ServiceDiscovery.Dns.Tests.Fuzzing" />
<InternalsVisibleTo Include="Microsoft.Extensions.ServiceDiscovery.Yarp.Tests" />
</ItemGroup>

</Project>
Loading
Loading