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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will throw if we can't set this setting, it's consistent with other similar settings in LdapSessionOptions, but it could cause a new exception for existing folks who set Timeout (though I haven't seen this fail).

}

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