diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md index fbd3d65844..8084e40e1c 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md @@ -160,6 +160,7 @@ with QuickFIX, followed by an example. | `EndpointIdentificationAlgorithm` | Sets the endpoint identification algorithm. If the algorithm parameter is non-null, the endpoint identification/verification procedures must be handled during SSL/TLS handshaking. See [Endpoint Identification Algorithm Names](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#jssenames). | | | | `UseSNI` | Enables the SSL engine to use Server Name Indication (SNI). This option is only applicable for initiators. If provided, `SNIHostName` will be used as the server name. Otherwise, `SocketConnectHost` or `SocketConnectHost` will be used. Note: When this option is disabled, the JVM may still implicitly send the SSL `server_name` extension. | `Y`
`N` | `N` | | `SNIHostName` | SNI host name to be used as desired Server Name Indication (SNI) parameter. | | | +| `ReverseDNSEnabled` | Whether to perform a reverse DNS lookup when connected directly to/from an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). For initiators, disabling this option also avoids reverse DNS when recreating the remote socket address before connection. Applies to both initiator and acceptor SSL sessions. | `Y`
`N` | `Y` | --- diff --git a/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java b/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java new file mode 100644 index 0000000000..85dfcc6db7 --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java @@ -0,0 +1,16 @@ +package quickfix.mina; + +import java.net.InetSocketAddress; + +@FunctionalInterface +public interface HostResolutionStrategy { + String getHost(InetSocketAddress address); + + HostResolutionStrategy WITH_REVERSE_DNS = InetSocketAddress::getHostName; + + HostResolutionStrategy WITHOUT_REVERSE_DNS = InetSocketAddress::getHostString; + + static HostResolutionStrategy fromReverseDnsEnabled(boolean reverseDnsEnabled) { + return reverseDnsEnabled ? WITH_REVERSE_DNS : WITHOUT_REVERSE_DNS; + } +} diff --git a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java index e5a9afd13b..dfb998a2e6 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java +++ b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java @@ -41,6 +41,7 @@ import quickfix.SessionSettings; import quickfix.mina.CompositeIoFilterChainBuilder; import quickfix.mina.EventHandlingStrategy; +import quickfix.mina.HostResolutionStrategy; import quickfix.mina.NetworkingOptions; import quickfix.mina.ProtocolFactory; import quickfix.mina.SessionConnector; @@ -135,7 +136,9 @@ private void installSSL(AcceptorSocketDescriptor descriptor, log.info("Installing SSL filter for {}", descriptor.getAddress()); SSLConfig sslConfig = descriptor.getSslConfig(); SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - SslFilter sslFilter = new AcceptorSslFilter(sslContext); + HostResolutionStrategy hostResolutionStrategy = HostResolutionStrategy.fromReverseDnsEnabled( + descriptor.isReverseDnsEnabled()); + SslFilter sslFilter = new AcceptorSslFilter(sslContext, hostResolutionStrategy); sslFilter.setNeedClientAuth(sslConfig.isNeedClientAuth()); sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); @@ -188,6 +191,8 @@ && getSettings().getBool(sessionID, SSLSupport.SETTING_USE_SSL)) { sslConfig = SSLSupport.getSslConfig(getSettings(), sessionID); } } + boolean reverseDnsEnabled = getSettings().getBoolOrDefault(sessionID, + SSLSupport.SETTING_REVERSE_DNS_ENABLED, true); int acceptPort = (int) settings.getLong(sessionID, Acceptor.SETTING_SOCKET_ACCEPT_PORT); @@ -202,11 +207,12 @@ && getSettings().getBool(sessionID, SSLSupport.SETTING_USE_SSL)) { // Check for cached descriptor AcceptorSocketDescriptor descriptor = socketDescriptorForAddress.get(acceptorAddress); if (descriptor != null) { - if (descriptor.isUseSSL() != useSSL || !equals(sslConfig, descriptor.getSslConfig())) { + if (descriptor.isUseSSL() != useSSL || !equals(sslConfig, descriptor.getSslConfig()) + || descriptor.isReverseDnsEnabled() != reverseDnsEnabled) { throw new ConfigError("Conflicting configurations of acceptor socket: " + acceptorAddress); } } else { - descriptor = new AcceptorSocketDescriptor(acceptorAddress, useSSL, sslConfig); + descriptor = new AcceptorSocketDescriptor(acceptorAddress, useSSL, sslConfig, reverseDnsEnabled); socketDescriptorForAddress.put(acceptorAddress, descriptor); } @@ -281,12 +287,15 @@ private static class AcceptorSocketDescriptor { private final SocketAddress address; private final boolean useSSL; private final SSLConfig sslConfig; + private final boolean reverseDnsEnabled; private final Map acceptedSessions = new HashMap<>(); - public AcceptorSocketDescriptor(SocketAddress address, boolean useSSL, SSLConfig sslConfig) { + public AcceptorSocketDescriptor(SocketAddress address, boolean useSSL, SSLConfig sslConfig, + boolean reverseDnsEnabled) { this.address = address; this.useSSL = useSSL; this.sslConfig = sslConfig; + this.reverseDnsEnabled = reverseDnsEnabled; } public void acceptSession(Session session) { @@ -308,6 +317,10 @@ public boolean isUseSSL() { public SSLConfig getSslConfig() { return sslConfig; } + + public boolean isReverseDnsEnabled() { + return reverseDnsEnabled; + } } public Collection getEndpoints() { diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java index 96c7975c7e..649d3fca14 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java @@ -38,6 +38,7 @@ import quickfix.SessionSettings; import quickfix.field.converter.BooleanConverter; import quickfix.mina.EventHandlingStrategy; +import quickfix.mina.HostResolutionStrategy; import quickfix.mina.NetworkingOptions; import quickfix.mina.ProtocolFactory; import quickfix.mina.SessionConnector; @@ -139,6 +140,10 @@ private void createInitiator(final Session session, final boolean continueInitOn sslConfig = SSLSupport.getSslConfig(getSettings(), sessionID); } + // Defaults to true for backwards compatibility + HostResolutionStrategy hostResolutionStrategy = HostResolutionStrategy.fromReverseDnsEnabled( + getSettings().getBoolOrDefault(sessionID, SSLSupport.SETTING_REVERSE_DNS_ENABLED, true)); + String proxyUser = null; String proxyPassword = null; String proxyHost = null; @@ -177,7 +182,7 @@ && getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_DOMAIN)) { ScheduledExecutorService scheduledExecutorService = (scheduledReconnectExecutor != null ? scheduledReconnectExecutor : getScheduledExecutorService()); try { final IoSessionInitiator ioSessionInitiator = new IoSessionInitiator(session, - socketAddresses, localAddress, connectTimeout, reconnectingIntervals, + socketAddresses, localAddress, hostResolutionStrategy, connectTimeout, reconnectingIntervals, scheduledExecutorService, settings, networkingOptions, getEventHandlingStrategy(), getIoFilterChainBuilder(), sslEnabled, sslConfig, proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, proxyWorkstation); diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java index 9c782dc01b..84adf56e18 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -35,6 +35,7 @@ import quickfix.SystemTime; import quickfix.mina.CompositeIoFilterChainBuilder; import quickfix.mina.EventHandlingStrategy; +import quickfix.mina.HostResolutionStrategy; import quickfix.mina.NetworkingOptions; import quickfix.mina.ProtocolFactory; import quickfix.mina.SessionConnector; @@ -64,10 +65,10 @@ public class IoSessionInitiator { private Future reconnectFuture; - public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, - SocketAddress localAddress, int connectTimeout, int[] reconnectIntervalInSeconds, - ScheduledExecutorService executor, SessionSettings sessionSettings, NetworkingOptions networkingOptions, - EventHandlingStrategy eventHandlingStrategy, + IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, + SocketAddress localAddress, HostResolutionStrategy hostResolutionStrategy, int connectTimeout, + int[] reconnectIntervalInSeconds, ScheduledExecutorService executor, SessionSettings sessionSettings, + NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, IoFilterChainBuilder userIoFilterChainBuilder, boolean sslEnabled, SSLConfig sslConfig, String proxyType, String proxyVersion, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, String proxyDomain, String proxyWorkstation) throws ConfigError { @@ -83,7 +84,8 @@ public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, reconnectTask = new ConnectTask(sslEnabled, socketAddresses, localAddress, userIoFilterChainBuilder, fixSession, connectTimeoutMillis, reconnectIntervalInMillis, sessionSettings, networkingOptions, eventHandlingStrategy, sslConfig, - proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, proxyWorkstation, log); + proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, + proxyWorkstation, log, hostResolutionStrategy); } catch (GeneralSecurityException e) { throw new ConfigError(e); } @@ -105,6 +107,7 @@ private static class ConnectTask implements Runnable { private final EventHandlingStrategy eventHandlingStrategy; private final SSLConfig sslConfig; private final Logger log; + private final HostResolutionStrategy hostResolutionStrategy; private IoSession ioSession; private long lastReconnectAttemptTime; @@ -122,13 +125,13 @@ private static class ConnectTask implements Runnable { private final String proxyDomain; private final String proxyWorkstation; - public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, + ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, SocketAddress localAddress, IoFilterChainBuilder userIoFilterChainBuilder, Session fixSession, long connectTimeoutMillis, long[] reconnectIntervalInMillis, SessionSettings sessionSettings, NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, SSLConfig sslConfig, String proxyType, String proxyVersion, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, String proxyDomain, - String proxyWorkstation, Logger log) throws ConfigError, GeneralSecurityException { + String proxyWorkstation, Logger log, HostResolutionStrategy hostResolutionStrategy) throws ConfigError, GeneralSecurityException { this.sslEnabled = sslEnabled; this.socketAddresses = socketAddresses; this.localAddress = localAddress; @@ -141,6 +144,7 @@ public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, this.eventHandlingStrategy = eventHandlingStrategy; this.sslConfig = sslConfig; this.log = log; + this.hostResolutionStrategy = hostResolutionStrategy; this.proxyType = proxyType; this.proxyVersion = proxyVersion; @@ -194,7 +198,7 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { private void installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder) throws GeneralSecurityException { final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - final SslFilter sslFilter = new InitiatorSslFilter(sslContext, getSniHostName(sslConfig)); + final SslFilter sslFilter = new InitiatorSslFilter(sslContext, getSniHostName(sslConfig), hostResolutionStrategy); sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() @@ -294,7 +298,7 @@ private SocketAddress getNextSocketAddress() { // Recreate socket address to avoid cached address resolution if (socketAddress instanceof InetSocketAddress) { InetSocketAddress inetAddr = (InetSocketAddress) socketAddress; - socketAddress = new InetSocketAddress(inetAddr.getHostName(), inetAddr.getPort()); + socketAddress = new InetSocketAddress(hostResolutionStrategy.getHost(inetAddr), inetAddr.getPort()); socketAddresses[nextSocketAddressIndex] = socketAddress; } nextSocketAddressIndex = (nextSocketAddressIndex + 1) % socketAddresses.length; diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/AbstractSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/AbstractSslFilter.java new file mode 100644 index 0000000000..75af7ee8c7 --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/AbstractSslFilter.java @@ -0,0 +1,71 @@ +package quickfix.mina.ssl; + +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; +import quickfix.mina.HostResolutionStrategy; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import java.net.InetSocketAddress; + +abstract class AbstractSslFilter extends SslFilter { + private final HostResolutionStrategy hostResolutionStrategy; + + AbstractSslFilter(SSLContext sslContext, boolean autoStart, + HostResolutionStrategy hostResolutionStrategy) { + super(sslContext, autoStart); + this.hostResolutionStrategy = hostResolutionStrategy; + } + + @Override + protected final SSLEngine createEngine(IoSession session, InetSocketAddress addr) { + SSLEngine sslEngine; + + if (addr != null) { + sslEngine = sslContext.createSSLEngine(hostResolutionStrategy.getHost(addr), addr.getPort()); + } else { + sslEngine = sslContext.createSSLEngine(); + } + + if (wantClientAuth) { + sslEngine.setWantClientAuth(true); + } + + if (needClientAuth) { + sslEngine.setNeedClientAuth(true); + } + + if (enabledCipherSuites != null) { + sslEngine.setEnabledCipherSuites(enabledCipherSuites); + } + + if (enabledProtocols != null) { + sslEngine.setEnabledProtocols(enabledProtocols); + } + + if (getEndpointIdentificationAlgorithm() != null) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm()); + sslEngine.setSSLParameters(sslParameters); + } + + configureEngine(sslEngine); + sslEngine.setUseClientMode(!session.isServer()); + + return sslEngine; + } + + protected void configureEngine(SSLEngine sslEngine) { + } + + @Override + public final void sessionClosed(NextFilter next, IoSession session) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Session {} closed", session); + } + + onClose(next, session, true); + next.sessionClosed(session); + } +} diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java index a1e4268e96..642af1bad5 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java @@ -1,23 +1,11 @@ package quickfix.mina.ssl; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; +import quickfix.mina.HostResolutionStrategy; import javax.net.ssl.SSLContext; -public final class AcceptorSslFilter extends SslFilter { - - public AcceptorSslFilter(SSLContext sslContext) { - super(sslContext); - } - - @Override - public void sessionClosed(NextFilter next, IoSession session) throws Exception { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("SERVER: Session {} closed", session); - } - - onClose(next, session, true); - next.sessionClosed(session); +public final class AcceptorSslFilter extends AbstractSslFilter { + public AcceptorSslFilter(SSLContext sslContext, HostResolutionStrategy hostResolutionStrategy) { + super(sslContext, true, hostResolutionStrategy); } } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java index 5faa65248c..8a72ee733a 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java @@ -1,74 +1,31 @@ package quickfix.mina.ssl; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; +import quickfix.mina.HostResolutionStrategy; import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; -import java.net.InetSocketAddress; import java.util.Collections; -public final class InitiatorSslFilter extends SslFilter { +/** + * This class is not part of the public API and may be removed or changed in future releases. + */ +public final class InitiatorSslFilter extends AbstractSslFilter { private final String sniHostName; - public InitiatorSslFilter(SSLContext sslContext, String sniHostName) { - super(sslContext, false); + public InitiatorSslFilter(SSLContext sslContext, String sniHostName, HostResolutionStrategy hostResolutionStrategy) { + super(sslContext, false, hostResolutionStrategy); this.sniHostName = sniHostName; } @Override - protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) { - SSLEngine sslEngine; - - if (addr != null) { - sslEngine = sslContext.createSSLEngine(addr.getHostName(), addr.getPort()); - } else { - sslEngine = sslContext.createSSLEngine(); - } - - if (wantClientAuth) { - sslEngine.setWantClientAuth(true); - } - - if (needClientAuth) { - sslEngine.setNeedClientAuth(true); - } - - if (enabledCipherSuites != null) { - sslEngine.setEnabledCipherSuites(enabledCipherSuites); - } - - if (enabledProtocols != null) { - sslEngine.setEnabledProtocols(enabledProtocols); - } - - if (getEndpointIdentificationAlgorithm() != null) { - SSLParameters sslParameters = sslEngine.getSSLParameters(); - sslParameters.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm()); - sslEngine.setSSLParameters(sslParameters); - } - + protected void configureEngine(SSLEngine sslEngine) { if (sniHostName != null) { SSLParameters sslParameters = sslEngine.getSSLParameters(); sslParameters.setServerNames(Collections.singletonList(new SNIHostName(sniHostName))); sslEngine.setSSLParameters(sslParameters); } - - sslEngine.setUseClientMode(!session.isServer()); - - return sslEngine; - } - - @Override - public void sessionClosed(NextFilter next, IoSession session) throws Exception { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("CLIENT: Session {} closed", session); - } - - onClose(next, session, true); - next.sessionClosed(session); } } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java index c8a0e6f5dd..71fe4d5a28 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java @@ -41,6 +41,7 @@ public class SSLSupport { public static final String SETTING_NEED_CLIENT_AUTH = "NeedClientAuth"; public static final String SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM = "EndpointIdentificationAlgorithm"; public static final String SETTING_USE_SNI = "UseSNI"; + public static final String SETTING_REVERSE_DNS_ENABLED = "ReverseDNSEnabled"; public static final String SETTING_SNI_HOST_NAME = "SNIHostName"; public static final String SETTING_ENABLED_PROTOCOLS = "EnabledProtocols"; public static final String SETTING_CIPHER_SUITES = "CipherSuites"; diff --git a/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java new file mode 100644 index 0000000000..30d2055161 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java @@ -0,0 +1,400 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix.mina.initiator; + +import org.apache.mina.util.AvailablePortFinder; +import org.burningwave.tools.net.DefaultHostResolver; +import org.burningwave.tools.net.HostResolutionRequestInterceptor; +import org.burningwave.tools.net.MappedHostResolver; +import org.junit.After; +import org.junit.Test; +import quickfix.Acceptor; +import quickfix.ApplicationAdapter; +import quickfix.ConfigError; +import quickfix.DefaultMessageFactory; +import quickfix.FixVersions; +import quickfix.Initiator; +import quickfix.MemoryStoreFactory; +import quickfix.MessageFactory; +import quickfix.MessageStoreFactory; +import quickfix.Session; +import quickfix.SessionFactory; +import quickfix.SessionID; +import quickfix.SessionSettings; +import quickfix.ThreadedSocketAcceptor; +import quickfix.ThreadedSocketInitiator; +import quickfix.mina.HostResolutionStrategy; +import quickfix.mina.ProtocolFactory; +import quickfix.mina.SessionConnector; +import quickfix.mina.ssl.SSLSupport; +import quickfix.test.util.SessionUtil; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * End-to-end test for {@link HostResolutionStrategy} configurations. + * + * @see SSLSupport#SETTING_REVERSE_DNS_ENABLED + */ +public class HostResolutionStrategyTest { + + private static final String FAKE_HOSTNAME = "fixserver.example.quickfixj"; + private static final String LOOPBACK_IP = "127.0.0.1"; + private static final byte[] LOOPBACK_IP_BYTES = new byte[]{127, 0, 0, 1}; + private static final String QUICKFIXJ_KEY_STORE = "quickfixj.keystore"; + private static final String QUICKFIXJ_KEY_STORE_PASSWORD = "quickfixjpw"; + + private static final SessionID INITIATOR_SESSION_ID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"); + private static final SessionID ACCEPTOR_SESSION_ID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"); + private static final SessionID SECOND_ACCEPTOR_SESSION_ID = new SessionID( + FixVersions.BEGINSTRING_FIX44, "BRAVO", "ZULU"); + + private TestAcceptor acceptor; + private TestInitiator initiator; + private BlockingReverseDnsResolver reverseDnsResolver; + + @After + public void tearDown() throws InterruptedException { + if (reverseDnsResolver != null) { + reverseDnsResolver.releaseReverseDns(); + } + if (initiator != null) { + initiator.stop(); + } + if (acceptor != null) { + acceptor.stop(); + } + Thread.sleep(500); + HostResolutionRequestInterceptor.INSTANCE.uninstall(); + } + + // --- When reverse DNS is available --- + + @Test + public void initiatorShouldLogonWhenConnectingToIpAddressWithReverseDnsEnabled() throws Exception { + assertInitiatorLogsOnWhenReverseDnsAvailable(LOOPBACK_IP, "Y"); + } + + @Test + public void initiatorShouldLogonWhenConnectingToIpAddressWithReverseDnsDisabled() throws Exception { + assertInitiatorLogsOnWhenReverseDnsAvailable(LOOPBACK_IP, "N"); + } + + @Test + public void initiatorShouldLogonWhenConnectingToCustomHostAliasWithReverseDnsEnabled() throws Exception { + assertInitiatorLogsOnWhenReverseDnsAvailable(FAKE_HOSTNAME, "Y"); + } + + @Test + public void initiatorShouldLogonWhenConnectingToCustomHostAliasWithReverseDnsDisabled() throws Exception { + assertInitiatorLogsOnWhenReverseDnsAvailable(FAKE_HOSTNAME, "N"); + } + + // --- When reverse DNS is unavailable --- + + @Test + public void initiatorShouldAttemptToCallReverseDnsDuringLogonWhenReverseDnsIsEnabled() throws Exception { + installBlockingResolver(); + int port = AvailablePortFinder.getNextAvailable(); + + acceptor = new TestAcceptor(createAcceptorSettings(port, "N")); + acceptor.start(); + + initiator = new TestInitiator(createInitiatorSettings(LOOPBACK_IP, port, "Y")); + initiator.start(); + + assertTrue(reverseDnsResolver.awaitReverseDnsLookup(5, TimeUnit.SECONDS)); + initiator.assertNotLoggedOn(INITIATOR_SESSION_ID, 1, TimeUnit.SECONDS); + acceptor.assertNotLoggedOn(ACCEPTOR_SESSION_ID, 1, TimeUnit.SECONDS); + + reverseDnsResolver.releaseReverseDns(); + + initiator.assertLoggedOn(INITIATOR_SESSION_ID); + acceptor.assertLoggedOn(ACCEPTOR_SESSION_ID); + assertEquals(1, reverseDnsResolver.getReverseDnsLookupCount()); + } + + @Test + public void initiatorShouldLogonImmediatelyWhenReverseDnsIsDisabled() throws Exception { + installBlockingResolver(); + int port = AvailablePortFinder.getNextAvailable(); + + acceptor = new TestAcceptor(createAcceptorSettings(port, "N")); + acceptor.start(); + + initiator = new TestInitiator(createInitiatorSettings(LOOPBACK_IP, port, "N")); + initiator.start(); + + initiator.assertLoggedOn(INITIATOR_SESSION_ID); + acceptor.assertLoggedOn(ACCEPTOR_SESSION_ID); + assertFalse(reverseDnsResolver.awaitReverseDnsLookup(250, TimeUnit.MILLISECONDS)); + assertEquals(0, reverseDnsResolver.getReverseDnsLookupCount()); + } + + @Test + public void acceptorShouldAttemptToCallReverseDnsDuringLogonWhenReverseDnsIsEnabled() throws Exception { + installBlockingResolver(); + int port = AvailablePortFinder.getNextAvailable(); + + acceptor = new TestAcceptor(createAcceptorSettings(port, "Y")); + acceptor.start(); + + initiator = new TestInitiator(createInitiatorSettings(LOOPBACK_IP, port, "N")); + initiator.start(); + + assertTrue(reverseDnsResolver.awaitReverseDnsLookup(5, TimeUnit.SECONDS)); + initiator.assertNotLoggedOn(INITIATOR_SESSION_ID, 1, TimeUnit.SECONDS); + acceptor.assertNotLoggedOn(ACCEPTOR_SESSION_ID, 1, TimeUnit.SECONDS); + + reverseDnsResolver.releaseReverseDns(); + + initiator.assertLoggedOn(INITIATOR_SESSION_ID); + acceptor.assertLoggedOn(ACCEPTOR_SESSION_ID); + assertEquals(1, reverseDnsResolver.getReverseDnsLookupCount()); + } + + @Test + public void initiatorAndAcceptorShouldAttemptToCallReverseDnsDuringLogonWhenReverseDnsIsEnabled() + throws Exception { + installBlockingResolver(); + int port = AvailablePortFinder.getNextAvailable(); + + acceptor = new TestAcceptor(createAcceptorSettings(port, "Y")); + acceptor.start(); + + initiator = new TestInitiator(createInitiatorSettings(LOOPBACK_IP, port, "Y")); + initiator.start(); + + assertTrue(reverseDnsResolver.awaitReverseDnsLookup(5, TimeUnit.SECONDS)); + initiator.assertNotLoggedOn(INITIATOR_SESSION_ID, 1, TimeUnit.SECONDS); + acceptor.assertNotLoggedOn(ACCEPTOR_SESSION_ID, 1, TimeUnit.SECONDS); + + reverseDnsResolver.releaseReverseDns(); + + initiator.assertLoggedOn(INITIATOR_SESSION_ID); + acceptor.assertLoggedOn(ACCEPTOR_SESSION_ID); + assertEquals(2, reverseDnsResolver.getReverseDnsLookupCount()); + } + + @Test(expected = ConfigError.class) + public void acceptorShouldRejectConflictingReverseDnsSettingsOnSameSocket() throws Exception { + SessionSettings settings = createAcceptorSettings(AvailablePortFinder.getNextAvailable(), "Y"); + settings.setString(SECOND_ACCEPTOR_SESSION_ID, "BeginString", FixVersions.BEGINSTRING_FIX44); + settings.setString(SECOND_ACCEPTOR_SESSION_ID, "DataDictionary", "FIX44.xml"); + settings.setString(SECOND_ACCEPTOR_SESSION_ID, "SenderCompID", "BRAVO"); + settings.setString(SECOND_ACCEPTOR_SESSION_ID, "TargetCompID", "ZULU"); + settings.setString(SECOND_ACCEPTOR_SESSION_ID, SSLSupport.SETTING_REVERSE_DNS_ENABLED, "N"); + + acceptor = new TestAcceptor(settings); + acceptor.start(); + } + + // --- Helpers --- + + private void assertInitiatorLogsOnWhenReverseDnsAvailable(String connectHost, String reverseDnsEnabled) + throws Exception { + installAvailableResolver(); + int port = AvailablePortFinder.getNextAvailable(); + + acceptor = new TestAcceptor(createAcceptorSettings(port)); + acceptor.start(); + + initiator = new TestInitiator(createInitiatorSettings(connectHost, port, reverseDnsEnabled)); + initiator.start(); + + initiator.assertLoggedOn(INITIATOR_SESSION_ID); + acceptor.assertLoggedOn(ACCEPTOR_SESSION_ID); + } + + private void installAvailableResolver() { + Map hostAliases = new HashMap<>(); + hostAliases.put(FAKE_HOSTNAME, LOOPBACK_IP); + HostResolutionRequestInterceptor.INSTANCE.install(new MappedHostResolver(hostAliases), DefaultHostResolver.INSTANCE); + } + + private void installBlockingResolver() { + Map hostAliases = new HashMap<>(); + hostAliases.put(FAKE_HOSTNAME, LOOPBACK_IP); + reverseDnsResolver = new BlockingReverseDnsResolver(hostAliases); + HostResolutionRequestInterceptor.INSTANCE.install(reverseDnsResolver, DefaultHostResolver.INSTANCE); + } + + static class TestAcceptor { + private final SessionConnector connector; + + TestAcceptor(SessionSettings settings) throws ConfigError { + MessageStoreFactory storeFactory = new MemoryStoreFactory(); + MessageFactory messageFactory = new DefaultMessageFactory(); + connector = new ThreadedSocketAcceptor(new ApplicationAdapter(), storeFactory, settings, messageFactory); + } + + void start() throws Exception { + connector.start(); + } + + void stop() { + connector.stop(true); + } + + void assertLoggedOn(SessionID sessionID) { + SessionUtil.assertLoggedOn(connector, sessionID); + } + + void assertNotLoggedOn(SessionID sessionID, long timeout, TimeUnit unit) { + SessionUtil.assertNotLoggedOn(connector, sessionID, timeout, unit); + } + } + + static class TestInitiator { + private final SessionConnector connector; + + TestInitiator(SessionSettings settings) throws ConfigError { + MessageStoreFactory storeFactory = new MemoryStoreFactory(); + MessageFactory messageFactory = new DefaultMessageFactory(); + connector = new ThreadedSocketInitiator(new ApplicationAdapter(), storeFactory, settings, messageFactory); + } + + void start() throws Exception { + connector.start(); + } + + void stop() { + connector.stop(true); + } + + void assertLoggedOn(SessionID sessionID) { + SessionUtil.assertLoggedOn(connector, sessionID); + } + + void assertNotLoggedOn(SessionID sessionID, long timeout, TimeUnit unit) { + SessionUtil.assertNotLoggedOn(connector, sessionID, timeout, unit); + } + } + + private static SessionSettings createAcceptorSettings(int port) { + return createAcceptorSettings(port, null); + } + + private static SessionSettings createAcceptorSettings(int port, String reverseDnsEnabled) { + HashMap defaults = new HashMap<>(); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); + defaults.put(Acceptor.SETTING_SOCKET_ACCEPT_PORT, Integer.toString(port)); + if (reverseDnsEnabled != null) { + defaults.put(SSLSupport.SETTING_REVERSE_DNS_ENABLED, reverseDnsEnabled); + } + defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); + defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, QUICKFIXJ_KEY_STORE); + defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, QUICKFIXJ_KEY_STORE_PASSWORD); + defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, QUICKFIXJ_KEY_STORE); + defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, QUICKFIXJ_KEY_STORE_PASSWORD); + defaults.put(SSLSupport.SETTING_NEED_CLIENT_AUTH, "N"); + + SessionSettings settings = new SessionSettings(); + settings.set(defaults); + settings.setString(ACCEPTOR_SESSION_ID, "BeginString", FixVersions.BEGINSTRING_FIX44); + settings.setString(ACCEPTOR_SESSION_ID, "DataDictionary", "FIX44.xml"); + settings.setString(ACCEPTOR_SESSION_ID, "SenderCompID", "ALFA"); + settings.setString(ACCEPTOR_SESSION_ID, "TargetCompID", "ZULU"); + + return settings; + } + + private static SessionSettings createInitiatorSettings(String connectHost, int port, String reverseDnsEnabled) { + HashMap defaults = new HashMap<>(); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "initiator"); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_HOST, connectHost); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PORT, Integer.toString(port)); + defaults.put(SSLSupport.SETTING_REVERSE_DNS_ENABLED, reverseDnsEnabled); + defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); + defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, QUICKFIXJ_KEY_STORE); + defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, QUICKFIXJ_KEY_STORE_PASSWORD); + defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, QUICKFIXJ_KEY_STORE); + defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, QUICKFIXJ_KEY_STORE_PASSWORD); + + SessionSettings settings = new SessionSettings(); + settings.set(defaults); + settings.setString(INITIATOR_SESSION_ID, "BeginString", FixVersions.BEGINSTRING_FIX44); + settings.setString(INITIATOR_SESSION_ID, "DataDictionary", "FIX44.xml"); + settings.setString(INITIATOR_SESSION_ID, "SenderCompID", "ZULU"); + settings.setString(INITIATOR_SESSION_ID, "TargetCompID", "ALFA"); + + return settings; + } + + static class BlockingReverseDnsResolver extends MappedHostResolver { + private final CountDownLatch reverseDnsLookupStarted = new CountDownLatch(1); + private final CountDownLatch reverseDnsLookupReleased = new CountDownLatch(1); + private final AtomicInteger reverseDnsLookupCount = new AtomicInteger(); + + BlockingReverseDnsResolver(Map hostAliases) { + super(hostAliases); + } + + @Override + public Collection getAllHostNamesForHostAddress(Map requestData) { + if (!Arrays.equals((byte[]) getMethodArguments(requestData)[0], LOOPBACK_IP_BYTES)) { + return super.getAllHostNamesForHostAddress(requestData); + } + + reverseDnsLookupCount.incrementAndGet(); + reverseDnsLookupStarted.countDown(); + + try { + if (!reverseDnsLookupReleased.await(5, TimeUnit.SECONDS)) { + throw new AssertionError("Timed out waiting to release reverse DNS lookup"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AssertionError("Interrupted while waiting to release reverse DNS lookup", e); + } + + return super.getAllHostNamesForHostAddress(requestData); + } + + boolean awaitReverseDnsLookup(long timeout, TimeUnit unit) throws InterruptedException { + return reverseDnsLookupStarted.await(timeout, unit); + } + + int getReverseDnsLookupCount() { + return reverseDnsLookupCount.get(); + } + + void releaseReverseDns() { + reverseDnsLookupReleased.countDown(); + } + } +} diff --git a/quickfixj-core/src/test/java/quickfix/mina/initiator/IoSessionInitiatorTest.java b/quickfixj-core/src/test/java/quickfix/mina/initiator/IoSessionInitiatorTest.java new file mode 100644 index 0000000000..20f9bb86e0 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/initiator/IoSessionInitiatorTest.java @@ -0,0 +1,60 @@ +package quickfix.mina.initiator; + +import org.apache.mina.transport.vmpipe.VmPipeAddress; +import org.junit.Test; +import quickfix.Log; +import quickfix.Session; +import quickfix.SessionSettings; +import quickfix.mina.EventHandlingStrategy; +import quickfix.mina.HostResolutionStrategy; +import quickfix.mina.NetworkingOptions; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IoSessionInitiatorTest { + + @Test + public void shouldNotConfigureProxyWhenTypeIsMissing() throws Exception { + assertProxyIsNotConfigured(new InetSocketAddress("127.0.0.1", 5001), null, 8080); + } + + @Test + public void shouldNotConfigureProxyWhenPortIsInvalid() throws Exception { + assertProxyIsNotConfigured(new InetSocketAddress("127.0.0.1", 5001), "invalid", -1); + } + + @Test + public void shouldNotConfigureProxyForVmPipeTransport() throws Exception { + assertProxyIsNotConfigured(new VmPipeAddress(5001), "invalid", 8080); + } + + private void assertProxyIsNotConfigured(SocketAddress socketAddress, String proxyType, int proxyPort) + throws Exception { + IoSessionInitiator initiator = createInitiator(socketAddress, proxyType, proxyPort); + try { + assertEquals(socketAddress, initiator.getSocketAddresses()[0]); + } finally { + initiator.stop(); + } + } + + private IoSessionInitiator createInitiator(SocketAddress socketAddress, String proxyType, int proxyPort) + throws Exception { + Session session = mock(Session.class); + when(session.getLog()).thenReturn(mock(Log.class)); + + return new IoSessionInitiator(session, new SocketAddress[] { socketAddress }, null, + HostResolutionStrategy.WITHOUT_REVERSE_DNS, 1, new int[] { 1 }, + mock(ScheduledExecutorService.class), new SessionSettings(), + new NetworkingOptions(new Properties()), mock(EventHandlingStrategy.class), null, + false, null, proxyType, "5", "127.0.0.1", proxyPort, + null, null, null, null); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java new file mode 100644 index 0000000000..79e85ffe8b --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java @@ -0,0 +1,119 @@ +package quickfix.mina.ssl; + +import org.apache.mina.core.session.IoSession; +import org.junit.Before; +import org.junit.Test; +import quickfix.mina.HostResolutionStrategy; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests that {@link AcceptorSslFilter} resolves the SSL engine peer host + * according to the configured {@link HostResolutionStrategy}. + */ +public class AcceptorSslFilterTest { + + private static final String LITERAL_IP = "1.2.3.4"; + private static final byte[] IP_BYTES = {1, 2, 3, 4}; + private static final String HOST_NAME = "example.quickfixj.org"; + private static final int PORT = 5001; + + private SSLContext sslContext; + private IoSession session; + + @Before + public void setUp() throws Exception { + sslContext = buildSslContext(); + session = mock(IoSession.class); + when(session.isServer()).thenReturn(true); + } + + @Test + public void shouldUseHostStringWhenReverseDnsDisabled() throws Exception { + AcceptorSslFilter filter = new AcceptorSslFilter(sslContext, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + + InetSocketAddress address = new InetSocketAddress(InetAddress.getByAddress(IP_BYTES), PORT); + + SSLEngine engine = filter.createEngine(session, address); + + assertEquals(LITERAL_IP, engine.getPeerHost()); + assertFalse(engine.getUseClientMode()); + } + + @Test + public void shouldUseHostNameWhenReverseDnsEnabled() throws Exception { + AcceptorSslFilter filter = new AcceptorSslFilter(sslContext, + HostResolutionStrategy.WITH_REVERSE_DNS); + + InetSocketAddress address = new InetSocketAddress( + InetAddress.getByAddress(HOST_NAME, IP_BYTES), PORT); + + SSLEngine engine = filter.createEngine(session, address); + + assertEquals(HOST_NAME, engine.getPeerHost()); + assertFalse(engine.getUseClientMode()); + } + + @Test + public void shouldCreateServerEngineWhenAddressIsNull() { + AcceptorSslFilter filter = new AcceptorSslFilter(sslContext, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + + SSLEngine engine = filter.createEngine(session, null); + + assertNotNull(engine); + assertFalse(engine.getUseClientMode()); + } + + @Test + public void shouldApplyOptionalEngineSettings() { + AcceptorSslFilter filter = new AcceptorSslFilter(sslContext, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + String cipherSuite = sslContext.getDefaultSSLParameters().getCipherSuites()[0]; + String protocol = sslContext.getDefaultSSLParameters().getProtocols()[0]; + filter.setWantClientAuth(true); + filter.setEnabledCipherSuites(cipherSuite); + filter.setEnabledProtocols(protocol); + filter.setEndpointIdentificationAlgorithm("HTTPS"); + + SSLEngine engine = filter.createEngine(session, null); + + assertTrue(engine.getWantClientAuth()); + assertEquals(cipherSuite, engine.getEnabledCipherSuites()[0]); + assertEquals(protocol, engine.getEnabledProtocols()[0]); + assertEquals("HTTPS", engine.getSSLParameters().getEndpointIdentificationAlgorithm()); + } + + @Test + public void shouldCreateClientEngineForClientSession() { + when(session.isServer()).thenReturn(false); + AcceptorSslFilter filter = new AcceptorSslFilter(sslContext, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + + SSLEngine engine = filter.createEngine(session, null); + + assertTrue(engine.getUseClientMode()); + } + + private static SSLContext buildSslContext() throws Exception { + SSLConfig sslConfig = new SSLConfig(); + sslConfig.setKeyStoreName(SSLSupport.QUICKFIXJ_KEY_STORE); + sslConfig.setKeyStorePassword(SSLSupport.QUICKFIXJ_KEY_STORE_PWD.toCharArray()); + sslConfig.setKeyStoreType("JKS"); + sslConfig.setKeyManagerFactoryAlgorithm("SunX509"); + sslConfig.setTrustStoreType("JKS"); + sslConfig.setTrustManagerFactoryAlgorithm("PKIX"); + return SSLContextFactory.getInstance(sslConfig); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/mina/ssl/InitiatorSslFilterTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/InitiatorSslFilterTest.java new file mode 100644 index 0000000000..ebc6749b4e --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/InitiatorSslFilterTest.java @@ -0,0 +1,92 @@ +package quickfix.mina.ssl; + +import org.apache.mina.core.session.IoSession; +import org.junit.Before; +import org.junit.Test; +import quickfix.mina.HostResolutionStrategy; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests that {@link InitiatorSslFilter} resolves the SSL engine peer host + * according to the configured {@link HostResolutionStrategy}. + */ +public class InitiatorSslFilterTest { + + private static final String LITERAL_IP = "1.2.3.4"; + private static final byte[] IP_BYTES = {1, 2, 3, 4}; + private static final String HOST_NAME = "example.quickfixj.org"; + private static final int PORT = 5001; + + private SSLContext sslContext; + private IoSession session; + + @Before + public void setUp() throws Exception { + sslContext = buildSslContext(); + session = mock(IoSession.class); + when(session.isServer()).thenReturn(false); + } + + @Test + public void shouldUseHostStringWhenReverseDnsDisabled() throws Exception { + InitiatorSslFilter filter = new InitiatorSslFilter(sslContext, null, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + + // Address built from raw bytes has no stored host name, so getHostString() + // returns the literal IP without performing a reverse DNS lookup. + InetSocketAddress address = new InetSocketAddress(InetAddress.getByAddress(IP_BYTES), PORT); + + SSLEngine engine = filter.createEngine(session, address); + + assertEquals(LITERAL_IP, engine.getPeerHost()); + assertTrue(engine.getUseClientMode()); + } + + @Test + public void shouldUseHostNameWhenReverseDnsEnabled() throws Exception { + InitiatorSslFilter filter = new InitiatorSslFilter(sslContext, null, + HostResolutionStrategy.WITH_REVERSE_DNS); + + // Address built with an explicit host name resolves to that name via + // getHostName() without performing a DNS lookup. + InetSocketAddress address = new InetSocketAddress( + InetAddress.getByAddress(HOST_NAME, IP_BYTES), PORT); + + SSLEngine engine = filter.createEngine(session, address); + + assertEquals(HOST_NAME, engine.getPeerHost()); + assertTrue(engine.getUseClientMode()); + } + + @Test + public void shouldCreateClientEngineWhenAddressIsNull() { + InitiatorSslFilter filter = new InitiatorSslFilter(sslContext, null, + HostResolutionStrategy.WITHOUT_REVERSE_DNS); + + SSLEngine engine = filter.createEngine(session, null); + + assertNotNull(engine); + assertTrue(engine.getUseClientMode()); + } + + private static SSLContext buildSslContext() throws Exception { + SSLConfig sslConfig = new SSLConfig(); + sslConfig.setKeyStoreName(SSLSupport.QUICKFIXJ_KEY_STORE); + sslConfig.setKeyStorePassword(SSLSupport.QUICKFIXJ_KEY_STORE_PWD.toCharArray()); + sslConfig.setKeyStoreType("JKS"); + sslConfig.setKeyManagerFactoryAlgorithm("SunX509"); + sslConfig.setTrustStoreType("JKS"); + sslConfig.setTrustManagerFactoryAlgorithm("PKIX"); + return SSLContextFactory.getInstance(sslConfig); + } +}