Java LDAP - TCP RST和套接字句柄泄漏

时间:2015-12-29 10:57:30

标签: java active-directory ldap

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

在某些情况下,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)    

1 个答案:

答案 0 :(得分:2)

主要问题在于:

  

此conn保持开放供将来使用。

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