From 42d48b18150df8610d1102398ec7a704e6a1e1f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:27:14 +0000 Subject: [PATCH 1/8] Initial plan From 9ef9a4d948310c8da501582b9c6c951fc53e1ce6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:57:16 +0000 Subject: [PATCH 2/8] Implement LDAP_OPT_NETWORK_TIMEOUT for Linux LDAP connections Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../Common/src/Interop/Interop.Ldap.cs | 1 + .../Interop/Linux/OpenLdap/Interop.Ldap.cs | 3 +++ .../Protocols/Interop/LdapPal.Linux.cs | 2 ++ .../Protocols/ldap/LdapConnection.Linux.cs | 22 ++++++++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Interop.Ldap.cs index 512242230093ff..986e72654992cc 100644 --- a/src/libraries/Common/src/Interop/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Interop.Ldap.cs @@ -156,6 +156,7 @@ internal enum LdapOption LDAP_OPT_SECURITY_CONTEXT = 0x99, LDAP_OPT_ROOTDSE_CACHE = 0x9a, // Not Supported in Linux LDAP_OPT_DEBUG_LEVEL = 0x5001, + LDAP_OPT_NETWORK_TIMEOUT = 0x5005, // Not Supported in Windows LDAP_OPT_URI = 0x5006, // Not Supported in Windows LDAP_OPT_X_TLS_CACERTDIR = 0x6003, // Not Supported in Windows LDAP_OPT_X_TLS_NEWCTX = 0x600F, // Not Supported in Windows diff --git a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs index 7a49a0d85448c9..dcb97ac0d64f65 100644 --- a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs @@ -171,6 +171,9 @@ public static partial int ldap_search( [LibraryImport(Libraries.OpenLdap, EntryPoint = "ldap_set_option")] public static partial int ldap_set_option_referral(ConnectionHandle ldapHandle, LdapOption option, ref LdapReferralCallback outValue); + [LibraryImport(Libraries.OpenLdap, EntryPoint = "ldap_set_option")] + public static partial int ldap_set_option_timeval(ConnectionHandle ldapHandle, LdapOption option, ref LDAP_TIMEVAL inValue); + // Note that ldap_start_tls_s has a different signature across Windows LDAP and OpenLDAP [LibraryImport(Libraries.OpenLdap, EntryPoint = "ldap_start_tls_s")] public static partial int ldap_start_tls(ConnectionHandle ldapHandle, IntPtr serverControls, IntPtr clientControls); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs index a74bff68535120..5826cab2bb6402 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs @@ -115,6 +115,8 @@ internal static int SearchDirectory(ConnectionHandle ldapHandle, string dn, int internal static int SetReferralOption(ConnectionHandle ldapHandle, LdapOption option, ref LdapReferralCallback outValue) => Interop.Ldap.ldap_set_option_referral(ldapHandle, option, ref outValue); + internal static int SetTimevalOption(ConnectionHandle ldapHandle, LdapOption option, ref LDAP_TIMEVAL inValue) => Interop.Ldap.ldap_set_option_timeval(ldapHandle, option, ref inValue); + // This option is not supported in Linux, so it would most likely throw. internal static int SetServerCertOption(ConnectionHandle ldapHandle, LdapOption option, VERIFYSERVERCERT outValue) => Interop.Ldap.ldap_set_option_servercert(ldapHandle, option, outValue); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs index ba5d2aebb1af67..bccb8a22e05cab 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs @@ -76,7 +76,27 @@ private int InternalConnectToServer() uris = $"{scheme}:{directoryIdentifier.PortNumber}"; } - return LdapPal.SetStringOption(_ldapHandle, LdapOption.LDAP_OPT_URI, uris); + int result = LdapPal.SetStringOption(_ldapHandle, LdapOption.LDAP_OPT_URI, uris); + if (result == 0) + { + // Set the network timeout option to honor the Timeout property + var timeout = new LDAP_TIMEVAL() + { + tv_sec = (int)(_connectionTimeOut.Ticks / TimeSpan.TicksPerSecond), + tv_usec = (int)((_connectionTimeOut.Ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond * 1000)) + }; + + int timeoutResult = LdapPal.SetTimevalOption(_ldapHandle, LdapOption.LDAP_OPT_NETWORK_TIMEOUT, ref timeout); + // If setting network timeout fails, we continue since it's not a critical error, + // but the original URI setting success should be preserved + if (timeoutResult != 0) + { + // Log or handle timeout setting failure if needed in the future + // For now, we don't fail the connection + } + } + + return result; } private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTITY_EX cred, BindMethod method) From 966a31085064e97393da884177a144769d75b5c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:03:06 +0000 Subject: [PATCH 3/8] Add tests to verify LDAP network timeout functionality on Linux Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../tests/LdapConnectionTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs index bab7e8291f183f..fddc719aac4e1d 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs @@ -307,6 +307,39 @@ public void Dispose_MultipleTimes_Nop() connection.Dispose(); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsLinux))] + public void NetworkTimeout_InitializationOnLinux_DoesNotThrow() + { + // This test verifies that initialization with network timeout on Linux doesn't cause issues + // We test that the LDAP connection can be created and timeout set without throwing exceptions + var connection = new LdapConnection("server") + { + Timeout = TimeSpan.FromSeconds(10) + }; + + // Verify the timeout is set correctly + Assert.Equal(TimeSpan.FromSeconds(10), connection.Timeout); + + // Dispose to clean up any native resources + connection.Dispose(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsLinux))] + public void NetworkTimeout_ZeroTimeoutOnLinux_DoesNotThrow() + { + // Test with zero timeout (infinite) which is a common edge case + var connection = new LdapConnection("server") + { + Timeout = TimeSpan.Zero + }; + + // Verify the timeout is set correctly + Assert.Equal(TimeSpan.Zero, connection.Timeout); + + // Dispose to clean up any native resources + connection.Dispose(); + } + public class CustomAsyncResult : IAsyncResult { public object AsyncState => throw new NotImplementedException(); From 1ab3a4cea953a91d88c41ad5e480c69f8bcf0424 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:11:22 +0000 Subject: [PATCH 4/8] Fix microseconds calculation for LDAP network timeout Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../Protocols/ldap/LdapConnection.Linux.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs index bccb8a22e05cab..206d2d9ba69b50 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs @@ -83,7 +83,7 @@ private int InternalConnectToServer() var timeout = new LDAP_TIMEVAL() { tv_sec = (int)(_connectionTimeOut.Ticks / TimeSpan.TicksPerSecond), - tv_usec = (int)((_connectionTimeOut.Ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond * 1000)) + tv_usec = (int)((_connectionTimeOut.Ticks % TimeSpan.TicksPerSecond) / 10) // Convert 100ns ticks to microseconds }; int timeoutResult = LdapPal.SetTimevalOption(_ldapHandle, LdapOption.LDAP_OPT_NETWORK_TIMEOUT, ref timeout); @@ -91,8 +91,9 @@ private int InternalConnectToServer() // but the original URI setting success should be preserved if (timeoutResult != 0) { - // Log or handle timeout setting failure if needed in the future - // For now, we don't fail the connection + // For debugging: we can add logging here in the future + // The timeout setting failed, but we don't fail the connection + // as network timeout might not be supported on all OpenLDAP versions } } From 75f43a47f40490b292780c39afe7e241eac7f7b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:10:04 +0000 Subject: [PATCH 5/8] Address PR feedback: improve tests and error handling Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../Protocols/ldap/LdapConnection.Linux.cs | 9 +--- .../tests/LdapConnectionTests.cs | 44 +++++++++++++------ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs index 206d2d9ba69b50..045b2923e0ad59 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs @@ -87,14 +87,7 @@ private int InternalConnectToServer() }; int timeoutResult = LdapPal.SetTimevalOption(_ldapHandle, LdapOption.LDAP_OPT_NETWORK_TIMEOUT, ref timeout); - // If setting network timeout fails, we continue since it's not a critical error, - // but the original URI setting success should be preserved - if (timeoutResult != 0) - { - // For debugging: we can add logging here in the future - // The timeout setting failed, but we don't fail the connection - // as network timeout might not be supported on all OpenLDAP versions - } + ErrorChecking.CheckAndSetLdapError(timeoutResult); } return result; diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs index fddc719aac4e1d..6da23c32a0c537 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Net; +using System.Net.Sockets; using System.Threading; using Xunit; @@ -307,39 +308,56 @@ public void Dispose_MultipleTimes_Nop() connection.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsLinux))] - public void NetworkTimeout_InitializationOnLinux_DoesNotThrow() + [Fact] + public void NetworkTimeout_Initialization_DoesNotThrow() { - // This test verifies that initialization with network timeout on Linux doesn't cause issues - // We test that the LDAP connection can be created and timeout set without throwing exceptions var connection = new LdapConnection("server") { Timeout = TimeSpan.FromSeconds(10) }; - // Verify the timeout is set correctly Assert.Equal(TimeSpan.FromSeconds(10), connection.Timeout); - - // Dispose to clean up any native resources connection.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsLinux))] - public void NetworkTimeout_ZeroTimeoutOnLinux_DoesNotThrow() + [Fact] + public void NetworkTimeout_ZeroTimeout_DoesNotThrow() { - // Test with zero timeout (infinite) which is a common edge case var connection = new LdapConnection("server") { Timeout = TimeSpan.Zero }; - // Verify the timeout is set correctly Assert.Equal(TimeSpan.Zero, connection.Timeout); - - // Dispose to clean up any native resources connection.Dispose(); } + [Fact] + public void NetworkTimeout_UnreachableServer_ThrowsTimeoutException() + { + // Use TEST-NET-1 address (192.0.2.x) which is reserved for documentation and testing + // and guaranteed to be unreachable, causing the connection to timeout + const string unreachableServer = "192.0.2.1"; + var connection = new LdapConnection(unreachableServer) + { + Timeout = TimeSpan.FromSeconds(2) // Short timeout to make test faster + }; + + try + { + // Attempt to bind should timeout due to unreachable server + var ex = Assert.ThrowsAny(() => connection.Bind()); + + // The exact exception type may vary by platform but should indicate a timeout/connection failure + Assert.True(ex is LdapException or TimeoutException or SocketException, + $"Expected LdapException, TimeoutException, or SocketException but got {ex.GetType().Name}"); + } + finally + { + connection.Dispose(); + } + } + public class CustomAsyncResult : IAsyncResult { public object AsyncState => throw new NotImplementedException(); From 4ccb9ae6ac4d3a72e6329fcf52490b7a03d73783 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:15:18 +0000 Subject: [PATCH 6/8] Mark unreachable server timeout test as OuterLoop Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../tests/LdapConnectionTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs index 6da23c32a0c537..30aed971b343df 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs @@ -333,6 +333,7 @@ public void NetworkTimeout_ZeroTimeout_DoesNotThrow() } [Fact] + [OuterLoop("Uses unreachable server which may take up to 2 seconds to timeout")] public void NetworkTimeout_UnreachableServer_ThrowsTimeoutException() { // Use TEST-NET-1 address (192.0.2.x) which is reserved for documentation and testing From 72a8a0067b7c40cdf064bea5e3b037d895b60ac7 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Thu, 11 Sep 2025 08:20:14 -0700 Subject: [PATCH 7/8] Update src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs --- .../tests/LdapConnectionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs index 30aed971b343df..6da23c32a0c537 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapConnectionTests.cs @@ -333,7 +333,6 @@ public void NetworkTimeout_ZeroTimeout_DoesNotThrow() } [Fact] - [OuterLoop("Uses unreachable server which may take up to 2 seconds to timeout")] public void NetworkTimeout_UnreachableServer_ThrowsTimeoutException() { // Use TEST-NET-1 address (192.0.2.x) which is reserved for documentation and testing From 4e86015e98a9628ce11d6eb286c0b68a4cb92726 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Thu, 11 Sep 2025 08:22:03 -0700 Subject: [PATCH 8/8] Update src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs index 045b2923e0ad59..48c814225b3764 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs @@ -83,7 +83,7 @@ private int InternalConnectToServer() var timeout = new LDAP_TIMEVAL() { tv_sec = (int)(_connectionTimeOut.Ticks / TimeSpan.TicksPerSecond), - tv_usec = (int)((_connectionTimeOut.Ticks % TimeSpan.TicksPerSecond) / 10) // Convert 100ns ticks to microseconds + tv_usec = (int)((_connectionTimeOut.Ticks % TimeSpan.TicksPerSecond) / TimeSpan.TicksPerMicrosecond) // Convert 100ns ticks to microseconds }; int timeoutResult = LdapPal.SetTimevalOption(_ldapHandle, LdapOption.LDAP_OPT_NETWORK_TIMEOUT, ref timeout);