diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
index 34bdc40148..02456b67c1 100644
--- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs
+++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
@@ -6782,6 +6782,9 @@ private void InitializeDirectConnectivity(IStoreClientFactory storeClientFactory
remoteCertificateValidationCallback: this.remoteCertificateValidationCallback,
distributedTracingOptions: distributedTracingOptions,
enableChannelMultiplexing: ConfigurationManager.IsTcpChannelMultiplexingEnabled(),
+ dnsResolutionFunction: ConfigurationManager.IsTcpDnsDotSuffixEnabled()
+ ? DnsDotSuffixHelper.ResolveHostAsync
+ : null,
chaosInterceptor: this.chaosInterceptor);
if (this.transportClientHandlerFactory != null)
diff --git a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
index b5b4d8eec2..93ce57f25b 100644
--- a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
+++ b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
@@ -125,6 +125,14 @@ internal static class ConfigurationManager
///
internal static readonly string UseLengthAwareRangeComparator = "AZURE_COSMOS_USE_LENGTH_AWARE_RANGE_COMPARATOR";
+ ///
+ /// Environment variable name to enable DNS dot-suffix (FQDN trailing dot) for
+ /// Direct mode TCP connections. When enabled, appends a trailing '.' to hostnames
+ /// before DNS resolution to bypass Kubernetes ndots search-domain expansion.
+ /// See: https://github.com/Azure/azure-cosmos-dotnet-v3/issues/5730
+ ///
+ internal static readonly string TcpDnsDotSuffixEnabled = "AZURE_COSMOS_TCP_DNS_DOT_SUFFIX_ENABLED";
+
public static T GetEnvironmentVariable(string variable, T defaultValue)
{
string value = Environment.GetEnvironmentVariable(variable);
@@ -401,5 +409,22 @@ public static bool IsLengthAwareRangeComparatorEnabled()
variable: ConfigurationManager.UseLengthAwareRangeComparator,
defaultValue: defaultValue);
}
+
+ ///
+ /// Gets the boolean value indicating if DNS dot-suffix (FQDN trailing dot) is enabled
+ /// for Direct mode TCP connections. When enabled, appends a trailing '.' to hostnames
+ /// before DNS resolution, causing the resolver to treat them as absolute (fully qualified)
+ /// names and skip search-domain expansion. This avoids unnecessary DNS lookups on Kubernetes
+ /// where ndots:5 causes multiple failed search-domain attempts for Cosmos DB endpoints.
+ /// Default: false (opt-in).
+ ///
+ /// A boolean flag indicating if TCP DNS dot-suffix is enabled.
+ public static bool IsTcpDnsDotSuffixEnabled()
+ {
+ return ConfigurationManager
+ .GetEnvironmentVariable(
+ variable: ConfigurationManager.TcpDnsDotSuffixEnabled,
+ defaultValue: false);
+ }
}
}
diff --git a/Microsoft.Azure.Cosmos/src/Util/DnsDotSuffixHelper.cs b/Microsoft.Azure.Cosmos/src/Util/DnsDotSuffixHelper.cs
new file mode 100644
index 0000000000..ae37f993f9
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/src/Util/DnsDotSuffixHelper.cs
@@ -0,0 +1,51 @@
+//------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------
+
+namespace Microsoft.Azure.Cosmos
+{
+ using System.Net;
+ using System.Threading.Tasks;
+ using Microsoft.Azure.Documents.Rntbd;
+
+ ///
+ /// Helper for DNS dot-suffix (FQDN trailing dot) resolution.
+ /// Appending a trailing dot to a hostname signals the DNS resolver that the name is
+ /// fully qualified (absolute) and must not be subject to search-domain expansion.
+ /// This avoids multiple unnecessary DNS lookups on Kubernetes where the default
+ /// ndots:5 configuration causes search-domain attempts for Cosmos DB endpoints.
+ ///
+ internal static class DnsDotSuffixHelper
+ {
+ ///
+ /// Appends a trailing dot to the hostname to make it a fully qualified domain name (FQDN).
+ /// Returns the hostname unchanged if it is null/empty, already ends with a dot,
+ /// or is an IP address (IPv4 or IPv6).
+ ///
+ /// The hostname to convert to FQDN form.
+ /// The hostname with a trailing dot appended, or unchanged if not applicable.
+ internal static string ToFqdnHostName(string hostName)
+ {
+ if (string.IsNullOrEmpty(hostName)
+ || hostName.EndsWith(".")
+ || IPAddress.TryParse(hostName, out _))
+ {
+ return hostName;
+ }
+
+ return hostName + ".";
+ }
+
+ ///
+ /// Creates a DNS resolution function that appends a trailing dot to the hostname
+ /// before resolving, to bypass Kubernetes ndots search-domain expansion.
+ /// Intended for use with StoreClientFactory.dnsResolutionFunction.
+ ///
+ /// A function that resolves a dot-suffixed hostname to an .
+ internal static Task ResolveHostAsync(string hostName)
+ {
+ string fqdnHost = DnsDotSuffixHelper.ToFqdnHostName(hostName);
+ return Connection.ResolveHostAsync(fqdnHost, includeIPv6Addresses: true);
+ }
+ }
+}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DnsDotSuffixHelperTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DnsDotSuffixHelperTests.cs
new file mode 100644
index 0000000000..71962e26d9
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DnsDotSuffixHelperTests.cs
@@ -0,0 +1,78 @@
+//------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------
+
+namespace Microsoft.Azure.Cosmos.Tests
+{
+ using System;
+ using System.Net;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class DnsDotSuffixHelperTests
+ {
+ [TestMethod]
+ public void ToFqdnHostName_AppendsTrailingDot()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("myaccount.documents.azure.com");
+ Assert.AreEqual("myaccount.documents.azure.com.", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_IdempotentWhenAlreadyDotSuffixed()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("myaccount.documents.azure.com.");
+ Assert.AreEqual("myaccount.documents.azure.com.", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_SkipsIPv4Address()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("10.0.0.1");
+ Assert.AreEqual("10.0.0.1", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_SkipsIPv6Address()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("::1");
+ Assert.AreEqual("::1", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_SkipsIPv6FullAddress()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ Assert.AreEqual("2001:0db8:85a3:0000:0000:8a2e:0370:7334", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_ReturnsNullForNull()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName(null);
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_ReturnsEmptyForEmpty()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName(string.Empty);
+ Assert.AreEqual(string.Empty, result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_AppendsTrailingDotToLocalhost()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("localhost");
+ Assert.AreEqual("localhost.", result);
+ }
+
+ [TestMethod]
+ public void ToFqdnHostName_AppendsTrailingDotToSingleLabel()
+ {
+ string result = DnsDotSuffixHelper.ToFqdnHostName("cosmosdb");
+ Assert.AreEqual("cosmosdb.", result);
+ }
+ }
+}