From 2d492e2bf0f0a7102437e178f567b58653276f3f Mon Sep 17 00:00:00 2001 From: tfitchie Date: Wed, 1 Jul 2026 08:28:55 +0100 Subject: [PATCH 1/9] Add reverse DNS lookup bypass --- .../src/main/java/quickfix/Initiator.java | 6 ++ .../quickfix/mina/HostResolutionStrategy.java | 12 +++ .../initiator/AbstractSocketInitiator.java | 7 +- .../mina/initiator/IoSessionInitiator.java | 18 ++-- .../quickfix/mina/ssl/InitiatorSslFilter.java | 7 +- .../mina/ssl/InitiatorSslFilterTest.java | 92 +++++++++++++++++++ 6 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java create mode 100644 quickfixj-core/src/test/java/quickfix/mina/ssl/InitiatorSslFilterTest.java diff --git a/quickfixj-core/src/main/java/quickfix/Initiator.java b/quickfixj-core/src/main/java/quickfix/Initiator.java index 8eabeee725..e2034d45a5 100644 --- a/quickfixj-core/src/main/java/quickfix/Initiator.java +++ b/quickfixj-core/src/main/java/quickfix/Initiator.java @@ -131,4 +131,10 @@ public interface Initiator extends Connector { */ String SETTING_DYNAMIC_SESSION = "DynamicSession"; + /** + * Whether the initiator should use reverse DNS to resolve + * the host when connecting directly to an IP address. + */ + String SETTING_REVERSE_DNS_ENABLED = "ReverseDNSEnabled"; + } 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..e4dd342cac --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java @@ -0,0 +1,12 @@ +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; +} 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..e53a745379 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 = getSettings().getBoolOrDefault(sessionID, Initiator.SETTING_REVERSE_DNS_ENABLED, true) ? + HostResolutionStrategy.WITH_REVERSE_DNS : HostResolutionStrategy.WITHOUT_REVERSE_DNS; + 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..79c632cc72 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; @@ -65,9 +66,9 @@ 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, + 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; @@ -128,7 +131,7 @@ public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, 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/InitiatorSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java index 5faa65248c..0b55987d57 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java @@ -2,6 +2,7 @@ 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; @@ -13,10 +14,12 @@ public final class InitiatorSslFilter extends SslFilter { private final String sniHostName; + private final HostResolutionStrategy hostResolutionStrategy; - public InitiatorSslFilter(SSLContext sslContext, String sniHostName) { + public InitiatorSslFilter(SSLContext sslContext, String sniHostName, HostResolutionStrategy hostResolutionStrategy) { super(sslContext, false); this.sniHostName = sniHostName; + this.hostResolutionStrategy = hostResolutionStrategy; } @Override @@ -24,7 +27,7 @@ protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) { SSLEngine sslEngine; if (addr != null) { - sslEngine = sslContext.createSSLEngine(addr.getHostName(), addr.getPort()); + sslEngine = sslContext.createSSLEngine(hostResolutionStrategy.getHost(addr), addr.getPort()); } else { sslEngine = sslContext.createSSLEngine(); } 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); + } +} From b1c14fc85086125a2fdb6193a0ad24dc2cef52fb Mon Sep 17 00:00:00 2001 From: tfitchie Date: Wed, 1 Jul 2026 12:09:41 +0100 Subject: [PATCH 2/9] Update configuration --- .../src/main/doc/usermanual/usage/configuration.html | 7 +++++++ .../src/main/doc/usermanual/usage/configuration.md | 1 + 2 files changed, 8 insertions(+) diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index 6d9fcfe6c9..379be1077a 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -591,6 +591,13 @@

QuickFIX Settings

Y
N N + + ReverseDNSEnabled + + Whether the initiator performs a reverse DNS lookup to resolve the peer host name when connecting directly to an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). When disabled, the literal IP address is used as-is and no reverse DNS lookup is performed. Only used with a SocketInitiator. + Y
N + Y + Acceptor diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md index fbd3d65844..5ca5ebc736 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md @@ -126,6 +126,7 @@ with QuickFIX, followed by an example. | `SocketLocalPort` | Bind the local socket to this port. Only used with a `SocketInitiator`. | positive integer | If unset the socket will be bound to a free port from the ephemeral port range. | | `SocketLocalHost` | Bind the local socket to this host. Only used with a `SocketInitiator`. | valid IP address in the format of `x.x.x.x` or a domain name | If unset the socket will be bound to all local interfaces. | | `DynamicSession` | Leave the corresponding session disconnected until `AbstractSocketInitiator.createDynamicSession` is called. | `Y`
`N` | `N` | +| `ReverseDNSEnabled` | Whether the initiator performs a reverse DNS lookup to resolve the peer host name when connecting directly to an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). When disabled, the literal IP address is used as-is and no reverse DNS lookup is performed. Only used with a `SocketInitiator`. | `Y`
`N` | `Y` | --- From e3f4826d572278c30cd90b13d79bc07ab044c00d Mon Sep 17 00:00:00 2001 From: tfitchie Date: Wed, 1 Jul 2026 12:19:47 +0100 Subject: [PATCH 3/9] document that initiatorSslFilter and ioSessionInitiator are internal classes --- .../main/java/quickfix/mina/initiator/IoSessionInitiator.java | 3 +++ .../src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java | 3 +++ 2 files changed, 6 insertions(+) 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 79c632cc72..2176db4dfd 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -57,6 +57,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * @internal This class is not part of the public API and may be removed or changed in future releases. + */ public class IoSessionInitiator { private final static long CONNECT_POLL_TIMEOUT = 2000L; private final ScheduledExecutorService executor; 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 0b55987d57..c365b82622 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java @@ -11,6 +11,9 @@ import java.net.InetSocketAddress; import java.util.Collections; +/** + * @internal This class is not part of the public API and may be removed or changed in future releases. + */ public final class InitiatorSslFilter extends SslFilter { private final String sniHostName; From 70da966d2f3b9ec7b3c673085c096c4b6faf4fdd Mon Sep 17 00:00:00 2001 From: tfitchie Date: Wed, 1 Jul 2026 13:05:38 +0100 Subject: [PATCH 4/9] Tidy up comments, remove config html change --- .../src/main/doc/usermanual/usage/configuration.html | 7 ------- .../java/quickfix/mina/initiator/IoSessionInitiator.java | 2 +- .../main/java/quickfix/mina/ssl/InitiatorSslFilter.java | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index 379be1077a..6d9fcfe6c9 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -591,13 +591,6 @@

QuickFIX Settings

Y
N N - - ReverseDNSEnabled - - Whether the initiator performs a reverse DNS lookup to resolve the peer host name when connecting directly to an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). When disabled, the literal IP address is used as-is and no reverse DNS lookup is performed. Only used with a SocketInitiator. - Y
N - Y - Acceptor 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 2176db4dfd..2747aab09b 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -58,7 +58,7 @@ import org.slf4j.LoggerFactory; /** - * @internal This class is not part of the public API and may be removed or changed in future releases. + * This class is not part of the public API and may be removed or changed in future releases. */ public class IoSessionInitiator { private final static long CONNECT_POLL_TIMEOUT = 2000L; 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 c365b82622..7428f3bf1b 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java @@ -12,7 +12,7 @@ import java.util.Collections; /** - * @internal This class is not part of the public API and may be removed or changed in future releases. + * This class is not part of the public API and may be removed or changed in future releases. */ public final class InitiatorSslFilter extends SslFilter { From fa1d094394581f140571852c9db7cd726920aa85 Mon Sep 17 00:00:00 2001 From: Christoph John Date: Wed, 1 Jul 2026 17:04:08 +0200 Subject: [PATCH 5/9] Reduced visibility of IoSessionInitiator constructor Removed comment about public API status and adjusted constructor parameters. --- .../java/quickfix/mina/initiator/IoSessionInitiator.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 2747aab09b..84adf56e18 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -57,9 +57,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * This class is not part of the public API and may be removed or changed in future releases. - */ public class IoSessionInitiator { private final static long CONNECT_POLL_TIMEOUT = 2000L; private final ScheduledExecutorService executor; @@ -68,7 +65,7 @@ public class IoSessionInitiator { private Future reconnectFuture; - public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, + IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, SocketAddress localAddress, HostResolutionStrategy hostResolutionStrategy, int connectTimeout, int[] reconnectIntervalInSeconds, ScheduledExecutorService executor, SessionSettings sessionSettings, NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, @@ -128,7 +125,7 @@ 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, From e9483ad3cce0090f0eaaec38b9926833fd58232f Mon Sep 17 00:00:00 2001 From: tfitchie Date: Mon, 20 Jul 2026 13:07:41 +0100 Subject: [PATCH 6/9] Add E2E tests + related features for the acceptor --- .../doc/usermanual/usage/configuration.md | 2 +- .../src/main/java/quickfix/Acceptor.java | 6 + .../quickfix/mina/HostResolutionStrategy.java | 4 + .../mina/acceptor/AbstractSocketAcceptor.java | 18 +- .../initiator/AbstractSocketInitiator.java | 4 +- .../quickfix/mina/ssl/AcceptorSslFilter.java | 45 ++- .../initiator/HostResolutionStrategyTest.java | 363 ++++++++++++++++++ .../mina/ssl/AcceptorSslFilterTest.java | 88 +++++ 8 files changed, 523 insertions(+), 7 deletions(-) create mode 100644 quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java create mode 100644 quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md index 5ca5ebc736..e5ce397fa6 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md @@ -126,7 +126,6 @@ with QuickFIX, followed by an example. | `SocketLocalPort` | Bind the local socket to this port. Only used with a `SocketInitiator`. | positive integer | If unset the socket will be bound to a free port from the ephemeral port range. | | `SocketLocalHost` | Bind the local socket to this host. Only used with a `SocketInitiator`. | valid IP address in the format of `x.x.x.x` or a domain name | If unset the socket will be bound to all local interfaces. | | `DynamicSession` | Leave the corresponding session disconnected until `AbstractSocketInitiator.createDynamicSession` is called. | `Y`
`N` | `N` | -| `ReverseDNSEnabled` | Whether the initiator performs a reverse DNS lookup to resolve the peer host name when connecting directly to an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). When disabled, the literal IP address is used as-is and no reverse DNS lookup is performed. Only used with a `SocketInitiator`. | `Y`
`N` | `Y` | --- @@ -161,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 to resolve the peer host name when connected directly to/from an IP address. The resolved host is used as the SSL peer host (e.g. for hostname verification). When disabled, the literal IP address is used as-is and no reverse DNS lookup is performed. Applies to both initiator and acceptor sessions. | `Y`
`N` | `Y` | --- diff --git a/quickfixj-core/src/main/java/quickfix/Acceptor.java b/quickfixj-core/src/main/java/quickfix/Acceptor.java index 434c4aa67e..e0879c8c7d 100644 --- a/quickfixj-core/src/main/java/quickfix/Acceptor.java +++ b/quickfixj-core/src/main/java/quickfix/Acceptor.java @@ -44,4 +44,10 @@ public interface Acceptor extends Connector { * Acceptor setting specifying a template acceptor session. */ String SETTING_ACCEPTOR_TEMPLATE = "AcceptorTemplate"; + + /** + * Whether the acceptor should use reverse DNS to resolve + * the peer host when accepting a connection directly from an IP address. + */ + String SETTING_REVERSE_DNS_ENABLED = "ReverseDNSEnabled"; } diff --git a/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java b/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java index e4dd342cac..85dfcc6db7 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java +++ b/quickfixj-core/src/main/java/quickfix/mina/HostResolutionStrategy.java @@ -9,4 +9,8 @@ public interface HostResolutionStrategy { 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..aaceb45a18 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, + Acceptor.SETTING_REVERSE_DNS_ENABLED, true); int acceptPort = (int) settings.getLong(sessionID, Acceptor.SETTING_SOCKET_ACCEPT_PORT); @@ -206,7 +211,7 @@ && getSettings().getBool(sessionID, SSLSupport.SETTING_USE_SSL)) { 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 +286,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 +316,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 e53a745379..b06363fe8f 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java @@ -141,8 +141,8 @@ private void createInitiator(final Session session, final boolean continueInitOn } // Defaults to true for backwards compatibility - HostResolutionStrategy hostResolutionStrategy = getSettings().getBoolOrDefault(sessionID, Initiator.SETTING_REVERSE_DNS_ENABLED, true) ? - HostResolutionStrategy.WITH_REVERSE_DNS : HostResolutionStrategy.WITHOUT_REVERSE_DNS; + HostResolutionStrategy hostResolutionStrategy = HostResolutionStrategy.fromReverseDnsEnabled( + getSettings().getBoolOrDefault(sessionID, Initiator.SETTING_REVERSE_DNS_ENABLED, true)); String proxyUser = null; String proxyPassword = null; 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..84ea889d45 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/AcceptorSslFilter.java @@ -2,13 +2,56 @@ 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; public final class AcceptorSslFilter extends SslFilter { + private final HostResolutionStrategy hostResolutionStrategy; - public AcceptorSslFilter(SSLContext sslContext) { + public AcceptorSslFilter(SSLContext sslContext, HostResolutionStrategy hostResolutionStrategy) { super(sslContext); + this.hostResolutionStrategy = hostResolutionStrategy; + } + + @Override + protected 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); + } + + sslEngine.setUseClientMode(!session.isServer()); + + return sslEngine; } @Override 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..c16c7628a6 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java @@ -0,0 +1,363 @@ +/******************************************************************************* + * 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 Initiator#SETTING_REVERSE_DNS_ENABLED + * @see Acceptor#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 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()); + } + + // --- 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(Acceptor.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(Initiator.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/ssl/AcceptorSslFilterTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java new file mode 100644 index 0000000000..c364c1425d --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/AcceptorSslFilterTest.java @@ -0,0 +1,88 @@ +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.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()); + } + + 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); + } +} From e8d23d7b5e5c30d9e594a4daf803b449393f986a Mon Sep 17 00:00:00 2001 From: tfitchie Date: Mon, 20 Jul 2026 13:12:54 +0100 Subject: [PATCH 7/9] tidy up --- quickfixj-core/src/main/java/quickfix/Acceptor.java | 6 ------ quickfixj-core/src/main/java/quickfix/Initiator.java | 6 ------ .../java/quickfix/mina/acceptor/AbstractSocketAcceptor.java | 2 +- .../quickfix/mina/initiator/AbstractSocketInitiator.java | 2 +- .../src/main/java/quickfix/mina/ssl/SSLSupport.java | 1 + 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/Acceptor.java b/quickfixj-core/src/main/java/quickfix/Acceptor.java index e0879c8c7d..434c4aa67e 100644 --- a/quickfixj-core/src/main/java/quickfix/Acceptor.java +++ b/quickfixj-core/src/main/java/quickfix/Acceptor.java @@ -44,10 +44,4 @@ public interface Acceptor extends Connector { * Acceptor setting specifying a template acceptor session. */ String SETTING_ACCEPTOR_TEMPLATE = "AcceptorTemplate"; - - /** - * Whether the acceptor should use reverse DNS to resolve - * the peer host when accepting a connection directly from an IP address. - */ - String SETTING_REVERSE_DNS_ENABLED = "ReverseDNSEnabled"; } diff --git a/quickfixj-core/src/main/java/quickfix/Initiator.java b/quickfixj-core/src/main/java/quickfix/Initiator.java index e2034d45a5..8eabeee725 100644 --- a/quickfixj-core/src/main/java/quickfix/Initiator.java +++ b/quickfixj-core/src/main/java/quickfix/Initiator.java @@ -131,10 +131,4 @@ public interface Initiator extends Connector { */ String SETTING_DYNAMIC_SESSION = "DynamicSession"; - /** - * Whether the initiator should use reverse DNS to resolve - * the host when connecting directly to an IP address. - */ - String SETTING_REVERSE_DNS_ENABLED = "ReverseDNSEnabled"; - } 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 aaceb45a18..477d62a3d6 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java +++ b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java @@ -192,7 +192,7 @@ && getSettings().getBool(sessionID, SSLSupport.SETTING_USE_SSL)) { } } boolean reverseDnsEnabled = getSettings().getBoolOrDefault(sessionID, - Acceptor.SETTING_REVERSE_DNS_ENABLED, true); + SSLSupport.SETTING_REVERSE_DNS_ENABLED, true); int acceptPort = (int) settings.getLong(sessionID, Acceptor.SETTING_SOCKET_ACCEPT_PORT); 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 b06363fe8f..649d3fca14 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java @@ -142,7 +142,7 @@ private void createInitiator(final Session session, final boolean continueInitOn // Defaults to true for backwards compatibility HostResolutionStrategy hostResolutionStrategy = HostResolutionStrategy.fromReverseDnsEnabled( - getSettings().getBoolOrDefault(sessionID, Initiator.SETTING_REVERSE_DNS_ENABLED, true)); + getSettings().getBoolOrDefault(sessionID, SSLSupport.SETTING_REVERSE_DNS_ENABLED, true)); String proxyUser = null; String proxyPassword = null; 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"; From faf465c66b9a1481c57e91ed64e587404a18a6ce Mon Sep 17 00:00:00 2001 From: tfitchie Date: Tue, 21 Jul 2026 08:43:40 +0100 Subject: [PATCH 8/9] fix mistake --- .../mina/initiator/HostResolutionStrategyTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java index c16c7628a6..a5a8ef384b 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java @@ -61,8 +61,8 @@ /** * End-to-end test for {@link HostResolutionStrategy} configurations. * - * @see Initiator#SETTING_REVERSE_DNS_ENABLED - * @see Acceptor#SETTING_REVERSE_DNS_ENABLED + * @see SSLSupport#SETTING_REVERSE_DNS_ENABLED + * @see SSLSupport#SETTING_REVERSE_DNS_ENABLED */ public class HostResolutionStrategyTest { @@ -273,7 +273,7 @@ private static SessionSettings createAcceptorSettings(int port, String reverseDn defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); defaults.put(Acceptor.SETTING_SOCKET_ACCEPT_PORT, Integer.toString(port)); if (reverseDnsEnabled != null) { - defaults.put(Acceptor.SETTING_REVERSE_DNS_ENABLED, reverseDnsEnabled); + 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); @@ -301,7 +301,7 @@ private static SessionSettings createInitiatorSettings(String connectHost, int p 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(Initiator.SETTING_REVERSE_DNS_ENABLED, reverseDnsEnabled); + 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); From d3098da8e96a8b7f429ad396ad808b3582da9ead Mon Sep 17 00:00:00 2001 From: tfitchie Date: Wed, 22 Jul 2026 14:19:49 +0100 Subject: [PATCH 9/9] Add both enabled test --- .../initiator/HostResolutionStrategyTest.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java index a5a8ef384b..48db2d4fdd 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/initiator/HostResolutionStrategyTest.java @@ -179,6 +179,29 @@ public void acceptorShouldAttemptToCallReverseDnsDuringLogonWhenReverseDnsIsEnab 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.awaitReverseDnsLookups(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()); + } + // --- Helpers --- private void assertInitiatorLogsOnWhenReverseDnsAvailable(String connectHost, String reverseDnsEnabled) @@ -319,12 +342,17 @@ private static SessionSettings createInitiatorSettings(String connectHost, int p } static class BlockingReverseDnsResolver extends MappedHostResolver { - private final CountDownLatch reverseDnsLookupStarted = new CountDownLatch(1); + private final CountDownLatch reverseDnsLookupStarted; private final CountDownLatch reverseDnsLookupReleased = new CountDownLatch(1); private final AtomicInteger reverseDnsLookupCount = new AtomicInteger(); BlockingReverseDnsResolver(Map hostAliases) { + this(hostAliases, 1); + } + + BlockingReverseDnsResolver(Map hostAliases, int expectedReverseDnsLookups) { super(hostAliases); + reverseDnsLookupStarted = new CountDownLatch(expectedReverseDnsLookups); } @Override @@ -349,6 +377,10 @@ public Collection getAllHostNamesForHostAddress(Map requ } boolean awaitReverseDnsLookup(long timeout, TimeUnit unit) throws InterruptedException { + return awaitReverseDnsLookups(timeout, unit); + } + + boolean awaitReverseDnsLookups(long timeout, TimeUnit unit) throws InterruptedException { return reverseDnsLookupStarted.await(timeout, unit); }