flamencist / ldap4net

OpenLdap port for DotNet Core (Linux\OSX\Windows)
MIT License
213 stars 38 forks source link

Can't connect to LDAP Server on Linux #136

Open AbhilashKopalli opened 2 years ago

AbhilashKopalli commented 2 years ago

Describe the bug I am able to connect to LDAP Server in the wndows but through docker container when I am using Linux Platform I am getting this error stating that Can't connect to LDAP Server.

LdapForNet.LdapException: Can't contact LDAP server. Result: -1.

To Reproduce It can be reproduced when we write a Dockerfile in linux environment.

Expected behavior Make it cross-platform compatible Desktop (please complete the following information):

Additional context I have tried to install the necessary libraries for LDAP as well for linux.

apt-get update && apt-get install -y ldap-utils krb5-kdc-ldap libsasl2-2 libsasl2-modules sasl2-bin libsasl2-modules-ldap openssl

Let me know if I am missing on anything. Thanks a lot in advance.

flamencist commented 2 years ago

Hi! Could you please provide the sample? What the type of auth do you use?

AbhilashKopalli commented 2 years ago

Hi Sure @flamencist,

I have tried using like this for authentication: Used simple and checked with digest as well but no luck.

connection.Bind(LdapAuthMechanism.SIMPLE, username, password);

Strangely I am running into this issue since today morning on Windows if there are huge number of records at SendRequestAsync function:-

LdapForNet.LdapUnavailableCriticalExtensionException: Unavailable Critical Extension. Unavailable Critical Extension. Result: 12. Method: ldap_parse_result. Details: ErrorMessage: 000020EF: SvcErr: DSID-03140594, problem 5010 (UNAVAIL_EXTENSION), data 0 at LdapForNet.Native.LdapNative.ThrowIfError(SafeHandle ld, Int32 res, String method, IDictionary`2 details) at LdapForNet.LdapConnection.ThrowIfResponseError(DirectoryResponse response) at LdapForNet.LdapConnection.SendRequestAsync(DirectoryRequest directoryRequest, CancellationToken token)

I have tried for returning records for last few days instead of everything, it works on Windows then and fails on Linux stating this error:

LdapForNet.LdapException: Can't contact LDAP server. Result: -1. Method: SearchRequest at LdapForNet.LdapConnection.ThrowIfResultError(DirectoryRequest directoryRequest, LdapResultType resType, DirectoryResponse directoryResponse) at LdapForNet.LdapConnection.ProcessResponse(DirectoryRequest directoryRequest, RequestHandler requestHandler, Int32 messageId, CancellationToken token) at LdapForNet.LdapConnection.<>c__DisplayClass24_0.b__0()

At this line,

var response = (SearchResponse)await connection.SendRequestAsync(directoryRequest);

Is there any code sample available that works for linux as well, I believe it requires some configuration changes. Not sure how to handle this, any help in that direction is much appreciated!

AbhilashKopalli commented 2 years ago

Any update Sir @flamencist on this?

flamencist commented 2 years ago

I think that you have a huge count of records in ldap server. Try to use pagination control

AbhilashKopalli commented 2 years ago

Tried using this, however it is failing at the first step of sendrequetasync. Not hitting the subsequent lines after it. Throwing an error

LdapForNet.LdapUnavailableCriticalExtensionException: Unavailable Critical Extension. Unavailable Critical Extension. Result: 12. Method: ldap_parse_result. Details: ErrorMessage: 000020EF: SvcErr: DSID-03140594, problem 5010 (UNAVAIL_EXTENSION), data 0

This is the code snippet I am trying to use from the documentation of ldapfornet.

            var directoryRequest = new SearchRequest(dnRoot, entryFilter, LdapSearchScope.LDAP_SCOPE_SUBTREE);
            var pageSize = 1000;
            var vlvRequestControl = new VlvRequestControl(0, pageSize - 1, 1);
            directoryRequest.Controls.Add(new SortRequestControl("name", false));
            directoryRequest.Controls.Add(vlvRequestControl);

            while (true)
            {
                var response = (SearchResponse)await connection.SendRequestAsync(directoryRequest);
                results.AddRange(response.Entries);
                var vlvResponseControl = (VlvResponseControl)response.Controls.Single(_ => _.GetType() == typeof(VlvResponseControl));
                vlvRequestControl.Offset += pageSize;
                if (vlvRequestControl.Offset > vlvResponseControl.ContentCount)
                {
                    break;
                }
            }
flamencist commented 2 years ago

I mean other control

using (var cn = new LdapConnection())
{
    var results = new List<DirectoryEntry>();
    cn.Connect();
    cn.Bind();
    var directoryRequest = new SearchRequest("dc=example,dc=com", "(objectclass=top)", LdapSearchScope.LDAP_SCOPE_SUB);
    var resultRequestControl = new PageResultRequestControl(3);
    directoryRequest.Controls.Add(resultRequestControl);

    var response = (SearchResponse)cn.SendRequest(directoryRequest);
    results.AddRange(response.Entries);

    PageResultResponseControl pageResultResponseControl;
    while (true)
    {
        pageResultResponseControl = (PageResultResponseControl)response.Controls.FirstOrDefault(_ => _ is PageResultResponseControl);
        if (pageResultResponseControl == null || pageResultResponseControl.Cookie.Length == 0)
        {
            break;
        }

        resultRequestControl.Cookie = pageResultResponseControl.Cookie;
        response = (SearchResponse)cn.SendRequest(directoryRequest);
        results.AddRange(response.Entries);
    }
    var entries = results.Select(_=>_.ToLdapEntry()).ToList();
}
AbhilashKopalli commented 2 years ago

Hi @flamencist,

Thanks I have tried this,

var resultRequestControl = new PageResultRequestControl(10000);

This always return 1000 records irrespective of what the pagesize is, is there anyway to override this functionality?

The solution was to add:

connection.SetOption(Native.LdapOption.LDAP_OPT_REFERRALS, IntPtr.Zero);

Which I happen to find it from MSDN article link, https://social.msdn.microsoft.com/Forums/vstudio/en-US/17957bb2-15b4-4d44-80fa-9b27eb6cb61f/pageresultrequestcontrol-cookie-always-zero?forum=csharpgeneral

Thanks a lot again, for guiding me through this issue.

AbhilashKopalli commented 2 years ago

Hello Sir @flamencist,

Back to the same error, when using pagination control. Any idea if we need any more configuration changes from our end?

Error: LdapForNet.LdapException: Can't contact LDAP server. Result: -1. Method: SearchRequest at LdapForNet.LdapConnection.ThrowIfResultError(DirectoryRequest directoryRequest, LdapResultType resType, DirectoryResponse directoryResponse) at LdapForNet.LdapConnection.ProcessResponse(DirectoryRequest directoryRequest, RequestHandler requestHandler, Int32 messageId, CancellationToken token) at LdapForNet.LdapConnection.<>c__DisplayClass24_0.<SendRequestAsync>b__0() at System.Threading.Tasks.Task1.InnerInvoke() at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj) at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionCont...`

This is the code snippet I am using currently:

            var results = new List<DirectoryEntry>();
            using var connection = connection(domainName); // Returns the controller with connect and bind

            var directoryRequest = new SearchRequest(dnRoot, entryFilter, LdapSearchScope.LDAP_SCOPE_SUB);
            var resultRequestControl = new PageResultRequestControl(500);
            directoryRequest.Controls.Add(resultRequestControl);

            connection.SetOption(Native.LdapOption.LDAP_OPT_REFERRALS, IntPtr.Zero);

            var response = (SearchResponse) await connection.SendRequestAsync(directoryRequest); // **This is where the error is**
            results.AddRange(response.Entries);

            PageResultResponseControl pageResultResponseControl;
            while (true)
            {
                pageResultResponseControl = (PageResultResponseControl)response.Controls.FirstOrDefault(_ => _ is PageResultResponseControl);
                if (pageResultResponseControl == null || pageResultResponseControl.Cookie.Length == 0)
                {
                    break;
                }

                resultRequestControl.Cookie = pageResultResponseControl.Cookie;
                response = (SearchResponse) await connection.SendRequestAsync(directoryRequest); 
                results.AddRange(response.Entries);
            }
            var entries = results.Select(_ => _.ToLdapEntry()).ToList();

            return ParseDirectoryEntryResult(results, dnRoot, entryFilter);
AbhilashKopalli commented 2 years ago

Hi @flamencist,

I have retried if it fails for the pagination and now I am encountering OutofMemory Exception. Is there any memory leak going on anywhere, the total records is 11000 and my pod memory size is 256MB. Any idea why this is happening?

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. at System.Collections.Generic.Dictionary2.Resize(Int32 newSize, Boolean forceNewHashCodes) at System.Collections.Generic.Dictionary2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Collections.ObjectModel.KeyedCollection2.AddKey(TKey key, TItem item) at LdapForNet.RequestHandlers.SearchRequestHandler.GetLdapAttributes(SafeHandle ld, IntPtr entry, IntPtr& ber) at LdapForNet.RequestHandlers.SearchRequestHandler.GetLdapEntries(SafeHandle ld, IntPtr msg, IntPtr ber)+MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) at LdapForNet.RequestHandlers.SearchRequestHandler.Handle(SafeHandle handle, LdapResultType resType, IntPtr msg, DirectoryResponse& response) at LdapForNet.LdapConnection.ProcessResponse(DirectoryRequest directoryRequest, RequestHandler requestHandler, Int32 messageId, CancellationToken token) at LdapForNet.LdapConnection.<>c__DisplayClass24_0.<SendRequestAsync>b__0() at System.Threading.Tasks.Task1.InnerInvoke() at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) `

denx08 commented 2 years ago

LdapForNet.LdapException: Can't contact LDAP server. Result: -1.

Hello @AbhilashKopalli ! Perhaps it will help you If TLS connection (LDAPS), use SetOption(LdapOption.LDAP_OPT_X_SASL_SSF_MAX, 256, false)

BalassaMarton commented 2 years ago

I'm having a very similar problem on Linux (RHEL7) when trying to reuse connections. Everything is fine if I'm disposing every LdapConnection after using it. However, when pooling the connection, after a few requests I get the same exception when sending a SearchRequest: Can't contact LDAP server. Result: -1. Method: SearchRequest. Is it possible that it is a memory leak in LdapForNet or OpenLDAP after all? To verify this, I tried using the same connection for every request, using a semaphore to make sure they are not run in parallel. I also did a full GC and logged the total process memory (Process.GetCurrentProcess().WorkingSet64) before and after each usage of the connection. Memory usage grows dramatically during the first few requests, and after that all requests fail with above error.

BalassaMarton commented 1 year ago

Not sure what happened, we upgraded to latest version since my last comment here, and the new exception is slightly different:

LdapForNet.LdapException: Can't contact LDAP server. Result: -1. Method: SearchRequestHandler
    at LdapForNet.Native.LdapNative.ThrowIfError(SafeHandle ld, Int32 res, String method, IDictionary`2 details)
    at LdapForNet.LdapConnection.SendRequest(DirectoryRequest directoryRequest, Int32& messageId)
    at LdapForNet.LdapConnection.SendRequestAsync(DirectoryRequest directoryRequest, CancellationToken token)

This is not the one and only exception we got. Seemingly randomly, sometimes the result is -2, and I even got errors like these:

ber_get_next: assertion `ber->ber_buf == ((void *)0)' failed

As if it was some random memory corruption. But it gets worse. I created a project that runs my test suite infinitely until at least one test fails, and creates memory snapshots before and after each test using dotMemory profiling API. Even this modified test runner can repro the above exceptions... unless I run it with dotMemory because then the process runs ad infinitum without producing any errors. It sure looks like something with native code, the native interop or something else that can be influenced by the profiler.

BalassaMarton commented 1 year ago

Update: I started investigation and might submit a PR if I find the root cause. I've found some issues around allocated buffers, but the errors didn't go away, only started occuring less frequently.

BalassaMarton commented 1 year ago

Update 2: Using libldap_r-2.4 made the buffer overflow and "Can't contact LDAP server" errors completely go away! Since 2.4 is an old version (albeit included in RHEL7 by default) and the 2.5 LTS version integrates libldap_r into libldap, this might solve issue of these esotheric errors. Memory usage is still a problem with an infinitely growing unmanaged heap.