-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding Linux support to System.DirectoryServices.Protocols #35380
Conversation
CC @tquerec |
...em.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/BerConverter.cs
Outdated
Show resolved
Hide resolved
...ctoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/OpenLdapUnsafeMethods.cs
Outdated
Show resolved
Hide resolved
...ryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
Outdated
Show resolved
Hide resolved
...em.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/BerConverter.cs
Outdated
Show resolved
Hide resolved
...toryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
Outdated
Show resolved
Hide resolved
...toryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
...ryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
Outdated
Show resolved
Hide resolved
...m.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/NativePal.Linux.cs
Outdated
Show resolved
Hide resolved
public const string LDAP_SASL_AUTOMATIC = "0"; | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CharSet = CharSet.Unicode [](start = 41, length = 25)
why this needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not sure, this was the way this struct already existed, I just moved them to a new file.
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
...Services.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs
Outdated
Show resolved
Hide resolved
...Services.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs
Outdated
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
...toryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
Outdated
Show resolved
Hide resolved
...toryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
Outdated
Show resolved
Hide resolved
...ctoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/OpenLdapUnsafeMethods.cs
Outdated
Show resolved
Hide resolved
SetHandle(ldap); | ||
} | ||
|
||
internal ConnectionHandle(IntPtr value, bool disposeHandle) : base(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
disposeHandle
here seems to be redundant with the one from the base class... which you're always giving the literal true
.
If it means "false to never call ReleaseHandle", that's exactly what the base bool ownsHandle
is for.
runtime/src/coreclr/src/vm/safehandle.cpp
Lines 114 to 136 in 5d5c36b
fPerformRelease = ((oldState & (SH_State_RefCount | SH_State_Closed)) == SH_RefCountOne) && m_ownsHandle; | |
if (fPerformRelease) | |
{ | |
GCPROTECT_BEGIN(sh); | |
CLR_BOOL fIsInvalid = FALSE; | |
DECLARE_ARGHOLDER_ARRAY(args, 1); | |
args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(sh); | |
PREPARE_SIMPLE_VIRTUAL_CALLSITE_USING_SLOT(s_IsInvalidHandleMethodSlot, sh); | |
CRITICAL_CALLSITE; | |
CALL_MANAGED_METHOD(fIsInvalid, CLR_BOOL, args); | |
if (fIsInvalid) | |
{ | |
fPerformRelease = false; | |
} | |
GCPROTECT_END(); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is your suggestion to basically instead of having a field for _needDispose and checking this in ReleaseHandle, I should just call base passing in disposeHandle value and not need to check in ReleaseHandle as it would not get called in the case it was set to false right? Just making sure I understood this comment..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we do have one problem with doing this. The one in the base I believe can't be overwritten after creation, and we have some (internal) methods in LdapConnection that can flip the _needsDispose, so I guess the reason why the original design was like this, was so that ReleaseHandle would get called always, and we would only actually dispose if we need to.
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
...DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/SafeHandles.Linux.cs
Outdated
Show resolved
Hide resolved
SetHandle(Wldap32.ldap_init(null, 389)); | ||
|
||
if (handle == IntPtr.Zero) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like the marshaller bypasses ctors when it creates SafeHandles as a return value, (maybe I'm wrong and it just finds a ctor that looks like one it knows how to call? or maybe a default is required and I'm having a very bad recollection day). I'm not sure this code actually prevents invalid handles.
A more canonical representation of this would be to declare the ldap_init call to return a SafeConnectionHandle (instead of IntPtr) and the caller should do
SafeConnectionHandle handle = LdapPal.Initialize(null, 389);
// In LdapPal.Windows.cs
internal static SafeConnectionHandle Initialize(string host, int port)
{
SafeConnectionHandle handle = Interop.wldap32.ldap_init(host, port);
if (handle.IsInvalid)
{
handle.Dispose();
// this error handling code
}
return handle;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like the marshaller bypasses ctors when it creates SafeHandles as a return value
Nope :)
e.g. try this:
using System;
using System.Runtime.InteropServices;
class Program
{
static void Main() => GetStdHandle(-10);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern MySafeHandle GetStdHandle(int nStdHandle);
}
class MySafeHandle : SafeHandle
{
public MySafeHandle(string hello, string world) : base(IntPtr.Zero, ownsHandle: false) { }
public override bool IsInvalid => true;
protected override bool ReleaseHandle() => true;
}
You'll get an exception like this:
Unhandled Exception: System.MissingMethodException: .ctor
at Program.GetStdHandle(Int32 nStdHandle)
at Program.Main()
…esn't yet have libldap installed
|
||
namespace System.DirectoryServices.Protocols | ||
{ | ||
internal sealed class ConnectionHandle : SafeHandleZeroOrMinusOneIsInvalid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is used by the P/Invoke declarations in Interop, I'd put this file in that same directory (similar to the Windows one). e.g. https://github.com/dotnet/runtime/blob/master/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs#L103-L134
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Following the OCSP example, you could just nestle the appropriate SafeHandle types in the bottom of the Interop.Ber.cs and Interop.Ldap.cs files, so that if some other library needs them it's just one include)
...toryServices.Protocols/src/System/DirectoryServices/Protocols/common/BerConverter.Windows.cs
Show resolved
Hide resolved
...em.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/BerConverter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.DirectoryServices.Protocols/tests/AsqRequestControlTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.DirectoryServices.Protocols/tests/BerConverterTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs
Outdated
Show resolved
Hide resolved
...ies/System.DirectoryServices.Protocols/tests/System.DirectoryServices.Protocols.Tests.csproj
Show resolved
Hide resolved
public static IEnumerable<object[]> Ctor_BeforeCount_AfterCount_ByteArrayTarget_Data() | ||
{ | ||
yield return new object[] { 0, 0, null, (PlatformDetection.IsWindows) ? new byte[] { 48, 132, 0, 0, 0, 18, 2, 1, 0, 2, 1, 0, 160, 132, 0, 0, 0, 6, 2, 1, 0, 2, 1, 0 } : new byte[] { 48, 14, 2, 1, 0, 2, 1, 0, 160, 6, 2, 1, 0, 2, 1, 0 } }; | ||
yield return new object[] { 10, 10, new byte[] { 1, 2, 3 }, (PlatformDetection.IsWindows) ? new byte[] { 48, 132, 0, 0, 0, 11, 2, 1, 10, 2, 1, 10, 129, 3, 1, 2, 3 } : new byte[] { 48, 11, 2, 1, 10, 2, 1, 10, 129, 3, 1, 2, 3 } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For long-term test-building maintenance, consider a helper. E.g.
yield return new object[] { 0, 0, null, FormatLengths("30{0}020100020100A0{1}020100020100", 18, 6) };
...
private static byte[] FormatLengths(string hexFormat, params int[] lengths)
{
string[] hexLengths = new string[lengths.Length];
for (int i = 0; i < lengths.Length; i++)
{
int curLen = lengths[i];
if (PlatformDetection.IsWindows)
{
hexLengths[i] = $"84{curLen:X8}";
}
else if (curLen < 0x80)
{
hexLengths[i] = curLen.ToString("X2");
}
else if (curLen <= byte.MaxValue)
{
hexLengths[i] = $"81{curLen:X2}";
}
else if (curLen <= ushort.MaxValue)
{
hexLengths[i] = $"82{curLen:X4}";
}
else if (curLen <= 0xFFFFFF)
{
hexLengths[i] = $"83{curLen:X6}";
}
else
{
hexLengths[i] = $"84{curLen:X8}";
}
}
string hex = string.Format(hexFormat, hexLengths);
// This presupposes something like the crypto tests have:
return hex.HexToByteArray();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this would be valuable, but if that's ok with you I would rather do that in a subsequent PR so to not add more complexity to this PR for now. Once I have tests running on Mono and OSX and passing, then I think it would be a great time to address this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferring the test helper is totally fine.
…orms that don't have libldap installed
...em.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/BerConverter.cs
Outdated
Show resolved
Hide resolved
Thanks a lot to all for the reviews! 😄 |
Nice! 🔥 |
🎆 |
Fixes #23944
Creating this as a draft PR first as I expect a lot of feedback plus some things will still need to happen in order to get tests working in Linux, but I want to start getting feedback so I can address it while I finish setting up running tests in Linux. This adds support to Linux for the System.DirectoryServices.Protocols namespace, by simply creating a PAL layer that abstracts the calls between wldap32 and openldap. Given both native libraries are just implementing ldap protocol, most of their APIs are the same with very similar or same structures which makes it so that managed code didn't actually have to change that much, we just change the native calls we do underneath. There are some open questions, like for example, there are some settings that are specific to wldap32 and not supported on any other library, the question is, should they be No-Ops for compatibility with previous apps working in Windows or should they instead throw PNSE?
One thing that is missing from this PR is the support for Default Credentials. That means that if you have your code running on a Kestrel Server which is domain-joined to an AD and have a valid session token already authenticated in the machine, then you wouldn't need to pass in credentials again when creating an LDAPConnection since you would just reuse that token. That works today in Windows, and I'm working on fixing the native calls on Bind to make sure that it also works on Linux. That support will come either as part of this PR later, or it will be added as a subsequent PR.
cc: @ericstj @tarekgh @bartonjs @stephentoub @davidsh
FYI: @JunTaoLuo @Tratcher