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

标签 java active-directory ldap

我们有一个 Java Ldap 客户端,它创建一个 conn 并绑定(bind)到 AD(Active Directory)。该连接保持开放以供将来使用。空闲超时(15 分钟)后,AD 通过发送 TCP RST 关闭 conn。当以后使用这样的 conn 时,LDAP 操作将如预期失败。对于此类故障,我们显式关闭 LDAP 连接。但是这些套接字句柄没有被释放,并最终在 lsof 输出中处于无法识别协议(protocol)状态。 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", "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b5c0c6d0c7f5d1dad8d4dcdb9bd6dad8" rel="noreferrer noopener nofollow">[email protected]</a>", "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)    

最佳答案

主要问题在这里:

This conn is kept open for future use.

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

关于Java LDAP - TCP RST 和套接字句柄泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34509550/

相关文章:

java - 单元测试resteasy时未注入(inject)@Context对象

ruby - 进入域后如何配置静默登录Redmine?

rest - Grails REST用户身份验证

active-directory - 什么 ldap 查询返回现在从事件目录中删除的用户对象?

regex - 以自定义格式保存 ldapsearch 的 Bash 脚本

java - 如何测试用户的输入以查看其单词是否在指定范围内

java - 如何调试第三方Intellij IDEA插件?

Windows 环境下的 Spring Security 单点登录

Java:从 MySQL 检索记录时结果集为空

java - 使用 AD 和 Apache shiro 从 LDAP 服务器检索全名或显示名称