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` |
---
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..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/InitiatorSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java
index 5faa65248c..7428f3bf1b 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;
@@ -10,13 +11,18 @@
import java.net.InetSocketAddress;
import java.util.Collections;
+/**
+ * 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;
+ 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 +30,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);
+ }
+}