Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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`<br/>`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`<br/>`N` | `Y` |

---

Expand Down
6 changes: 6 additions & 0 deletions quickfixj-core/src/main/java/quickfix/Initiator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";

}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Comment thread
chrjohn marked this conversation as resolved.
} catch (GeneralSecurityException e) {
throw new ConfigError(e);
}
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -10,21 +11,26 @@
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
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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading