Class SniAwareLdapSocketFactory
- All Implemented Interfaces:
Comparator<SniAwareLdapSocketFactory>
ldaps:// connections whose host name (e.g. a
CNAME, or a name backed by round-robin DNS) resolves to more than one
server address.
The JDK's default socket handling only ever connects to a single address
for a host name and never retries other addresses behind the same name if
that first attempt fails. This factory instead enumerates every address
behind the configured host name (via InetAddress.getAllByName(java.lang.String)) and
tries each in turn, while still performing the TLS handshake - SNI and
certificate hostname verification - against the original host name
rather than the numeric address actually connected to.
This distinction matters: verifying against a raw IP address instead of the
configured host name would silently defeat hostname verification (most
server certificates are not issued for IP addresses), which is why the
original host name is always passed through to the delegate factory's
SSLSocketFactory.createSocket(Socket, String, int, boolean).
Enabled by setting exo.ldap.sni.enabled=true; the connect timeout
(ms) used for each candidate address can be tuned with
exo.ldap.sni.connect.timeout. When unset, it falls back to whatever
com.sun.jndi.ldap.connect.timeout value is already configured -
either as a customJNDIConnectionParameters entry (the way this
timeout is shipped by default) or as a JVM system property - so switching
SNI on does not silently replace a timeout the admin already set, and
finally defaults to 10000 if neither is present. See
connectTimeoutMs() for the exact precedence.
Registered via the standard JNDI LDAP java.naming.ldap.factory.socket
connection property, whose contract requires a public static
getDefault() method (mirroring SSLSocketFactory.getDefault()) -
the JNDI LDAP provider obtains the factory instance by reflectively invoking
that method, not the class constructor.
Implements Comparator (comparing purely on class identity) because
com.sun.jndi.ldap.LdapPoolManager only allows pooling LDAP
connections that use a custom socket factory when that factory's class
implements java.util.Comparator - without it, enabling this
feature would silently disable com.sun.jndi.ldap.connect.pool.
-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionintcompare(SniAwareLdapSocketFactory first, SniAwareLdapSocketFactory second) All instances are considered equal: this is a stateless singleton, and the comparison only exists to satisfycom.sun.jndi.ldap.LdapPoolManager's pooling eligibility check.protected SocketconnectPlain(InetSocketAddress candidate, int timeoutMs) Opens a plain TCP connection to a single candidate address.static intResolves the connect timeout (ms) used for each candidate address, in order of precedence: the explicitexo.ldap.sni.connect.timeoutproperty, then whatevercom.sun.jndi.ldap.connect.timeoutvalue was hinted from the store'scustomJNDIConnectionParameters(seehintCustomJndiConnectTimeout(String)), then the JVM system property of the same name, then 10000.protected SocketconnectWithFailover(String host, int port) Overridable (rather than private) so tests can drive this method directly - including the TLS upgrade step - against a fake delegate factory, instead of only re-testing a copy of its retry loop.createSocket(String host, int port) createSocket(String host, int port, InetAddress localAddress, int localPort) createSocket(InetAddress host, int port) createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) createSocket(Socket s, String host, int port, boolean autoClose) protected SSLSocketFactorydelegate()Overridable (rather than private/static) so tests can substitute a fake TLS delegate and exerciseconnectWithFailover(String, int)without a real certificate/handshake.static SocketFactoryRequired by the JNDI LDAPjava.naming.ldap.factory.socketcontract: the configured class is instantiated by invoking this static method, not its constructor.String[]String[]static voidCalled once, when the SNI socket factory gets registered, with the rawcom.sun.jndi.ldap.connect.timeoutvalue found (if any) in the store'scustomJNDIConnectionParameters- this is how that timeout is actually shipped/configured (a JNDI env entry copied verbatim into the LDAP context), as opposed to a JVM system property.static booleanprotected List<InetSocketAddress> resolveCandidates(String host, int port) Enumerates every candidate address behindhost, paired with the target port.Methods inherited from class javax.net.ssl.SSLSocketFactory
createSocketMethods inherited from class javax.net.SocketFactory
createSocketMethods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface java.util.Comparator
equals, reversed, thenComparing, thenComparing, thenComparing, thenComparingDouble, thenComparingInt, thenComparingLong
-
Field Details
-
SNI_ENABLED_PROP
- See Also:
-
SNI_CONNECT_TIMEOUT_PROP
- See Also:
-
SOCKET_FACTORY_JNDI_PROPERTY
- See Also:
-
JNDI_LDAP_CONNECT_TIMEOUT_SYSTEM_PROP
- See Also:
-
DEFAULT_CONNECT_TIMEOUT_MS
public static final int DEFAULT_CONNECT_TIMEOUT_MS- See Also:
-
-
Constructor Details
-
SniAwareLdapSocketFactory
protected SniAwareLdapSocketFactory()
-
-
Method Details
-
getDefault
Required by the JNDI LDAPjava.naming.ldap.factory.socketcontract: the configured class is instantiated by invoking this static method, not its constructor. -
isEnabled
public static boolean isEnabled() -
hintCustomJndiConnectTimeout
Called once, when the SNI socket factory gets registered, with the rawcom.sun.jndi.ldap.connect.timeoutvalue found (if any) in the store'scustomJNDIConnectionParameters- this is how that timeout is actually shipped/configured (a JNDI env entry copied verbatim into the LDAP context), as opposed to a JVM system property. -
connectTimeoutMs
public static int connectTimeoutMs()Resolves the connect timeout (ms) used for each candidate address, in order of precedence: the explicitexo.ldap.sni.connect.timeoutproperty, then whatevercom.sun.jndi.ldap.connect.timeoutvalue was hinted from the store'scustomJNDIConnectionParameters(seehintCustomJndiConnectTimeout(String)), then the JVM system property of the same name, then 10000. -
compare
All instances are considered equal: this is a stateless singleton, and the comparison only exists to satisfycom.sun.jndi.ldap.LdapPoolManager's pooling eligibility check.- Specified by:
comparein interfaceComparator<SniAwareLdapSocketFactory>
-
createSocket
- Specified by:
createSocketin classSocketFactory- Throws:
IOException
-
createSocket
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException - Specified by:
createSocketin classSocketFactory- Throws:
IOException
-
createSocket
- Specified by:
createSocketin classSocketFactory- Throws:
IOException
-
createSocket
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException - Specified by:
createSocketin classSocketFactory- Throws:
IOException
-
createSocket
- Specified by:
createSocketin classSSLSocketFactory- Throws:
IOException
-
getDefaultCipherSuites
- Specified by:
getDefaultCipherSuitesin classSSLSocketFactory
-
getSupportedCipherSuites
- Specified by:
getSupportedCipherSuitesin classSSLSocketFactory
-
connectWithFailover
Overridable (rather than private) so tests can drive this method directly - including the TLS upgrade step - against a fake delegate factory, instead of only re-testing a copy of its retry loop.- Throws:
IOException
-
resolveCandidates
protected List<InetSocketAddress> resolveCandidates(String host, int port) throws UnknownHostException Enumerates every candidate address behindhost, paired with the target port. Overridable so tests can verify the mapping without depending on real multi-homed DNS entries.- Throws:
UnknownHostException
-
connectPlain
Opens a plain TCP connection to a single candidate address. Overridable so the failover/skip-on-failure iteration can be exercised with real loopback sockets in tests, without requiring a TLS handshake.- Throws:
IOException
-
delegate
Overridable (rather than private/static) so tests can substitute a fake TLS delegate and exerciseconnectWithFailover(String, int)without a real certificate/handshake.
-