【问题标题】:Java LDAP - TCP RST and socket handle leakJava LDAP - TCP RST 和套接字句柄泄漏
【发布时间】:2016-04-03 05:58:33
【问题描述】:

我们有一个 Java Ldap 客户端,它创建一个连接并绑定到 AD(Active Directory)。此连接保持开放以供将来使用。空闲超时(15 分钟)后,AD 通过发送 TCP RST 关闭连接。以后使用这样的 conn 时,ldap 操作将按预期失败。在此类失败时,我们会明确关闭 ldap 连接。但是这些套接字句柄没有被释放,最终在 lsof 输出中处于 无法识别协议 状态。 strace 表示在此类失败的 ldap 句柄上显式 close 不会导致套接字关闭系统调用。

在某些情况下,AD 通过发送 FIN 关闭空闲连接。在这种情况下,LDAP 库本身会完全关闭 conn,并且不会发生此问题。

这是 Java Ldap 库中的错误吗?有什么解决办法吗?

测试代码重现问题

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.InitialLdapContext;

public class Ldap {

        public static DirContext connect(String host, String port, String bindDn, String password) throws NamingException {
                        Hashtable<String, String> env = new Hashtable<String, String>();
                String ldapUrl = "ldap://" + host + ":" + port;

                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                env.put(Context.PROVIDER_URL, ldapUrl);
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                env.put(Context.SECURITY_PRINCIPAL, bindDn);
                env.put(Context.SECURITY_CREDENTIALS, password);
                env.put(Context.REFERRAL, "ignore");

            return new InitialLdapContext(env, null);
        }

        public static void main(String[] args) {
                try {
                        System.out.println("Ldap connect....");
                        DirContext conn = connect("ipaddress", "389", "user@domain.com", "password");
                        System.out.println("Sleep - active ldap conn");
                        Thread.sleep(18 * 60 * 1000);
                        System.out.println("Closing conn...");
                        conn.close();
                        System.out.println("Sleep for ever...");
                        Thread.sleep(60 * 10 * 60 * 1000);
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}

Ldap 线程(RST)

recvfrom(5, 0x7ffd27274540, 8192, 0, 0, 0) = -1 ECONNRESET (Connection reset by peer)
lseek(3, 43442010, SEEK_SET)            = 43442010
read(3, "PK\3\4\n\0\0\0\0\0\321\205\222E\241\375N\256\204\1\0\0\204\1\0\0&\0\0\0", 30) = 30
lseek(3, 43442078, SEEK_SET)            = 43442078
read(3, "\312\376\272\276\0\0\0003\0\25\1\0\3()V\1\0\25(Ljava/lang/S"..., 388) = 388
recvfrom(5, "", 8192, 0, NULL, NULL)    = 0
sendto(5, "0\5\2\1\2B\0", 7, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0xd)                       = -1 EPIPE (Broken pipe)
sendto(5, "0\5\2\1\2B\0", 7, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0xd)                       = -1 EPIPE (Broken pipe)
mmap(0x7ffd27185000, 12288, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7ffd27185000
rt_sigprocmask(SIG_SETMASK, [QUIT], NULL, 8) = 0
madvise(0x7ffd27185000, 1028096, MADV_DONTNEED) = 0
_exit(0)                                = ?
Process 47983 detached

Ldap 线程(FIN)

recvfrom(5, "", 8192, 0, NULL, NULL)    = 0
gettimeofday({1451376987, 341537}, NULL) = 0
sendto(5, "0\5\2\1\2B\0", 7, 0, NULL, 0) = 7
dup2(4, 5)                              = 5
close(5)                                = 0
mmap(0x7fc3f8984000, 12288, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fc3f8984000
gettimeofday({1451376987, 342129}, NULL) = 0
rt_sigprocmask(SIG_SETMASK, [QUIT], NULL, 8) = 0
madvise(0x7fc3f8984000, 1028096, MADV_DONTNEED) = 0
_exit(0)    

【问题讨论】:

  • 如果您想做严肃的 LDAP 工作,我建议您使用 LDAP SDK。 UnboundID 是我的最爱。
  • @jwilleke 我一直在读这篇文章,但我从来没有看到一个很好的理由。我正在做一些非常复杂的 LDAP 工作,所有这些都在 JNDI 中加上一些手写控件。如果 JNDI 支持全套 LDAP 控件,甚至是草稿控件,那就太好了,而且我有一个 RFE,但我不知道特殊用途的 SDK 可以解决的其他任何问题。
  • UnboundID Java SDK 将改变您的生活。由于它们是内置的,因此您将不再需要执行许多功能。例如,这些是“ServerSets”DNSSRVRecordServerSet、FailoverServerSet、FastestConnectServerSet、FewestConnectionsServerSet、RoundRobinDNSServerSet、RoundRobinServerSet、SingleServerSet,它们允许许多不同类型的池化和负载平衡能力。 docs.ldap.com/ldap-sdk/docs/javadoc/index.html.

标签: java active-directory ldap


【解决方案1】:

主要问题在这里:

此连接保持开放以供将来使用。

不要那样做。这是不好的做法。你在服务器上占用资源。您应该只在需要时获取 JNDI 上下文,然后立即关闭它们。您可以通过在 Java 代码中启用 JNDI LDAP connection pooling 来减轻其不利影响,但超时时间比 15 分钟要短得多。这样就不会出现接收 RST 的问题。我已经按照这些思路运行 LDAP 客户端六年了,没有任何泄漏。

【讨论】:

  • 好的。知道为什么 Java LDAP 客户端没有在 RST 上正确关闭套接字。这是 SDK 中的错误吗?如果我们在使用后很快关闭conn,这些socket不会进入TIME_WAIT状态(如果长时间连续爆发ldap请求可能会出现问题)。
  • @Anoop 它看起来像一个错误,如果我还记得我六年前所做的调查,我完全有可能已经遇到过它。您确实会得到 TIME_WAIT 状态,但泄漏套接字的替代方案更糟。您需要将连接池超时设置得足够高以最小化这种情况。我建议五到十分钟。
猜你喜欢
  • 1970-01-01
  • 2013-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-20
  • 1970-01-01
  • 1970-01-01
  • 2016-11-28
相关资源
最近更新 更多