Skip to content
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/Interop.Ldap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,21 @@ 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.TicksPerMicrosecond) // Convert 100ns ticks to microseconds
};

int timeoutResult = LdapPal.SetTimevalOption(_ldapHandle, LdapOption.LDAP_OPT_NETWORK_TIMEOUT, ref timeout);
ErrorChecking.CheckAndSetLdapError(timeoutResult);
}

return result;
}

private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTITY_EX cred, BindMethod method)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Xunit;

Expand Down Expand Up @@ -307,6 +308,56 @@ public void Dispose_MultipleTimes_Nop()
connection.Dispose();
}

[Fact]
public void NetworkTimeout_Initialization_DoesNotThrow()
{
var connection = new LdapConnection("server")
{
Timeout = TimeSpan.FromSeconds(10)
};

Assert.Equal(TimeSpan.FromSeconds(10), connection.Timeout);
connection.Dispose();
}

[Fact]
public void NetworkTimeout_ZeroTimeout_DoesNotThrow()
{
var connection = new LdapConnection("server")
{
Timeout = TimeSpan.Zero
};

Assert.Equal(TimeSpan.Zero, connection.Timeout);
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<Exception>(() => 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();
Expand Down
Loading