From 20204bbd2d07260cff15667bbeee23dca55261c9 Mon Sep 17 00:00:00 2001 From: Lubomir Sokolovsky <7549610+Rimobul@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:50:57 +0200 Subject: [PATCH 1/2] Fix DNS SRV provider ignoring query suffix Fixes #7175 Infer Kubernetes namespace in DnsSrvServiceEndpointProviderFactory. Add tests for suffix normalization and ServiceDomainNameCallback precedence. --- .../DnsSrvServiceEndpointProviderFactory.cs | 6 +- .../DnsSrvServiceEndpointResolverTests.cs | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs index ef593a7340c..6b5a0e00d58 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns/DnsSrvServiceEndpointProviderFactory.cs @@ -41,15 +41,15 @@ public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] ou var srvQuery = optionsValue.ServiceDomainNameCallback != null ? optionsValue.ServiceDomainNameCallback(query) - : DefaultServiceDomainNameCallback(query, optionsValue); + : DefaultServiceDomainNameCallback(query, _querySuffix!); provider = new DnsSrvServiceEndpointProvider(query, srvQuery, hostName: query.ServiceName, options, logger, resolver, timeProvider); return true; } - private static string DefaultServiceDomainNameCallback(ServiceEndpointQuery query, DnsSrvServiceEndpointProviderOptions options) + private static string DefaultServiceDomainNameCallback(ServiceEndpointQuery query, string querySuffix) { var portName = query.EndpointName ?? "default"; - return $"_{portName}._tcp.{query.ServiceName}.{options.QuerySuffix}"; + return $"_{portName}._tcp.{query.ServiceName}.{querySuffix}"; } private static string? GetKubernetesHostDomain() diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs index ec21bf9fa9c..6f2a925dd7e 100644 --- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs +++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs @@ -33,6 +33,86 @@ private sealed class FakeDnsResolver : IDnsResolver public ValueTask ResolveServiceAsync(string name, CancellationToken cancellationToken = default) => ResolveServiceAsyncFunc!.Invoke(name, cancellationToken); } + /// + /// Regression test for https://github.com/dotnet/extensions/issues/7175: the query suffix used to build the SRV + /// query must be the normalized one, which has its leading dot trimmed and falls back to the inferred Kubernetes domain. + /// + [Theory] + [InlineData(".ns")] + [InlineData("ns")] + public async Task ResolveServiceEndpoint_DnsSrv_UsesQuerySuffix(string querySuffix) + { + string? srvQuery = null; + var dnsClientMock = new FakeDnsResolver + { + ResolveServiceAsyncFunc = (name, cancellationToken) => + { + srvQuery = name; + ServiceResult[] response = [ + new ServiceResult(DateTime.UtcNow.AddSeconds(60), 99, 66, 8888, "srv-a", [new AddressResult(DateTime.UtcNow.AddSeconds(64), IPAddress.Parse("10.10.10.10"))]) + ]; + + return ValueTask.FromResult(response); + } + }; + var services = new ServiceCollection() + .AddSingleton(dnsClientMock) + .AddServiceDiscoveryCore() + .AddDnsSrvServiceEndpointProvider(options => options.QuerySuffix = querySuffix) + .BuildServiceProvider(); + var watcherFactory = services.GetRequiredService(); + ServiceEndpointWatcher watcher; + await using ((watcher = watcherFactory.CreateWatcher("http://basket")).ConfigureAwait(false)) + { + var tcs = new TaskCompletionSource(); + watcher.OnEndpointsUpdated = tcs.SetResult; + watcher.Start(); + var initialResult = await tcs.Task; + Assert.True(initialResult.ResolvedSuccessfully); + } + + Assert.Equal("_default._tcp.basket.ns", srvQuery); + } + + [Fact] + public async Task ResolveServiceEndpoint_DnsSrv_ServiceDomainNameCallbackTakesPrecedenceOverQuerySuffix() + { + string? srvQuery = null; + var dnsClientMock = new FakeDnsResolver + { + ResolveServiceAsyncFunc = (name, cancellationToken) => + { + srvQuery = name; + ServiceResult[] response = [ + new ServiceResult(DateTime.UtcNow.AddSeconds(60), 99, 66, 8888, "srv-a", [new AddressResult(DateTime.UtcNow.AddSeconds(64), IPAddress.Parse("10.10.10.10"))]) + ]; + + return ValueTask.FromResult(response); + } + }; + var services = new ServiceCollection() + .AddSingleton(dnsClientMock) + .AddServiceDiscoveryCore() + .AddDnsSrvServiceEndpointProvider(options => + { + options.QuerySuffix = ".ns"; + options.ServiceDomainNameCallback = query => $"{query.ServiceName}.service.consul"; + }) + .BuildServiceProvider(); + var watcherFactory = services.GetRequiredService(); + ServiceEndpointWatcher watcher; + await using ((watcher = watcherFactory.CreateWatcher("http://basket")).ConfigureAwait(false)) + { + var tcs = new TaskCompletionSource(); + watcher.OnEndpointsUpdated = tcs.SetResult; + watcher.Start(); + var initialResult = await tcs.Task; + Assert.True(initialResult.ResolvedSuccessfully); + } + + Assert.Equal("basket.service.consul", srvQuery); + } + [Fact] public async Task ResolveServiceEndpoint_DnsSrv() { From 65bcf3a7ec2c6cf0cffa02160d058d5c1c9526b5 Mon Sep 17 00:00:00 2001 From: Lubomir Sokolovsky Date: Mon, 10 Aug 2026 10:01:48 +0200 Subject: [PATCH 2/2] Adjust misleading comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../DnsSrvServiceEndpointResolverTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs index 6f2a925dd7e..8a9c01f9fb7 100644 --- a/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs +++ b/test/Libraries/Microsoft.Extensions.ServiceDiscovery.Dns.Tests/DnsSrvServiceEndpointResolverTests.cs @@ -35,7 +35,7 @@ private sealed class FakeDnsResolver : IDnsResolver /// /// Regression test for https://github.com/dotnet/extensions/issues/7175: the query suffix used to build the SRV - /// query must be the normalized one, which has its leading dot trimmed and falls back to the inferred Kubernetes domain. + /// query must be normalized (leading dot trimmed) before composing the SRV query. /// [Theory] [InlineData(".ns")]