diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index e735cf3ac2..bfc1592391 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -626,6 +626,58 @@

QuickFIX Settings

Java default cipher suites + + Socks Proxy Options (Initiator only) + + + ProxyType + Proxy type + http
socks + + + + ProxyVersion + Proxy HTTP or Socks version to use + For socks: 4, 4a or 5
For http: 1.0 or 1.1 + For socks:
For http: 1.0 + + + ProxyHost + Proxy server hostname or IP + valid IP address in the format of x.x.x.x or a domain name + + + + ProxyPort + Proxy server port + positive integer + + + + ProxyUser + Proxy user + + + + + ProxyPassword + Proxy password + + + + + ProxyDomain + Proxy domain (For http proxy) + + + + + ProxyWorkstation + Proxy workstation (For http proxy) + + + + Socket Options (Acceptor or Initiator) diff --git a/quickfixj-core/src/main/java/quickfix/Initiator.java b/quickfixj-core/src/main/java/quickfix/Initiator.java index 7dcab3020d..ffb39bf631 100644 --- a/quickfixj-core/src/main/java/quickfix/Initiator.java +++ b/quickfixj-core/src/main/java/quickfix/Initiator.java @@ -68,4 +68,52 @@ public interface Initiator extends Connector { * @see quickfix.SessionFactory#SETTING_CONNECTION_TYPE */ String SETTING_SOCKET_LOCAL_PORT = "SocketLocalPort"; + + /** + * Initiator setting for proxy type. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_TYPE = "ProxyType"; + + /** + * Initiator setting for proxy version. Only valid when session connection + * type is "initiator". - http 1.0 / 1.1 + */ + String SETTING_PROXY_VERSION = "ProxyVersion"; + + /** + * Initiator setting for proxy host. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_HOST = "ProxyHost"; + + /** + * Initiator setting for proxy port. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_PORT = "ProxyPort"; + + /** + * Initiator setting for proxy port. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_USER = "ProxyUser"; + + /** + * Initiator setting for proxy port. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_PASSWORD = "ProxyPassword"; + + /** + * Initiator setting for proxy domain. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_DOMAIN = "ProxyDomain"; + + /** + * Initiator setting for proxy workstation. Only valid when session connection + * type is "initiator". + */ + String SETTING_PROXY_WORKSTATION = "ProxyWorkstation"; } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java index 640247b74e..2046315577 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java @@ -21,14 +21,28 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; + import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoConnector; +import org.apache.mina.transport.socket.SocketConnector; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.mina.transport.vmpipe.VmPipeAcceptor; import org.apache.mina.transport.vmpipe.VmPipeAddress; import org.apache.mina.transport.vmpipe.VmPipeConnector; +import org.apache.mina.proxy.ProxyConnector; +import org.apache.mina.proxy.handlers.ProxyRequest; +import org.apache.mina.proxy.handlers.http.HttpAuthenticationMethods; +import org.apache.mina.proxy.handlers.http.HttpProxyConstants; +import org.apache.mina.proxy.handlers.http.HttpProxyRequest; +import org.apache.mina.proxy.handlers.socks.SocksProxyConstants; +import org.apache.mina.proxy.handlers.socks.SocksProxyRequest; +import org.apache.mina.proxy.session.ProxyIoSession; + import quickfix.ConfigError; import quickfix.RuntimeError; @@ -58,7 +72,7 @@ public static String getTypeString(int type) { public static SocketAddress createSocketAddress(int transportType, String host, int port) throws ConfigError { - if (transportType == SOCKET) { + if (transportType == SOCKET || transportType == PROXY) { return host != null ? new InetSocketAddress(host, port) : new InetSocketAddress(port); } else if (transportType == VM_PIPE) { return new VmPipeAddress(port); @@ -102,6 +116,109 @@ public static IoAcceptor createIoAcceptor(int transportType) { } } + public static ProxyConnector createIoProxyConnector(SocketConnector socketConnector, + InetSocketAddress address, + InetSocketAddress proxyAddress, + String proxyType, + String proxyVersion, + String proxyUser, + String proxyPassword, + String proxyDomain, + String proxyWorkstation ) throws ConfigError { + + // Create proxy connector. + ProxyRequest req; + + ProxyConnector connector = new ProxyConnector(socketConnector); + connector.setConnectTimeoutMillis(5000); + + if (proxyType.equalsIgnoreCase("http")) { + req = createHttpProxyRequest(address, proxyVersion, proxyUser, proxyPassword, proxyDomain, proxyWorkstation); + } else if (proxyType.equalsIgnoreCase("socks")) { + req = createSocksProxyRequest(address, proxyVersion, proxyUser, proxyPassword); + } else { + throw new ConfigError("Proxy type must be http or socks"); + } + + ProxyIoSession proxyIoSession = new ProxyIoSession(proxyAddress, req); + + List l = new ArrayList<>(); + l.add(HttpAuthenticationMethods.NO_AUTH); + l.add(HttpAuthenticationMethods.DIGEST); + l.add(HttpAuthenticationMethods.BASIC); + + proxyIoSession.setPreferedOrder(l); + connector.setProxyIoSession(proxyIoSession); + + return connector; + } + + + private static ProxyRequest createHttpProxyRequest(InetSocketAddress address, + String proxyVersion, + String proxyUser, + String proxyPassword, + String proxyDomain, + String proxyWorkstation) { + String uri = "http://" + address.getAddress().getHostAddress() + ":" + address.getPort(); + HashMap props = new HashMap<>(); + props.put(HttpProxyConstants.USER_PROPERTY, proxyUser); + props.put(HttpProxyConstants.PWD_PROPERTY, proxyPassword); + if (proxyDomain != null && proxyWorkstation != null) { + props.put(HttpProxyConstants.DOMAIN_PROPERTY, proxyDomain); + props.put(HttpProxyConstants.WORKSTATION_PROPERTY, proxyWorkstation); + } + + HttpProxyRequest req = new HttpProxyRequest(uri); + req.setProperties(props); + if (proxyVersion != null && proxyVersion.equalsIgnoreCase("1.1")) { + req.setHttpVersion(HttpProxyConstants.HTTP_1_1); + } else { + req.setHttpVersion(HttpProxyConstants.HTTP_1_0); + } + + return req; + } + + + private static ProxyRequest createSocksProxyRequest(InetSocketAddress address, + String proxyVersion, + String proxyUser, + String proxyPassword) throws ConfigError { + SocksProxyRequest req; + if (proxyVersion.equalsIgnoreCase("4")) { + req = new SocksProxyRequest( + SocksProxyConstants.SOCKS_VERSION_4, + SocksProxyConstants.ESTABLISH_TCPIP_STREAM, + address, + proxyUser); + + } else if (proxyVersion.equalsIgnoreCase("4a")) { + req = new SocksProxyRequest( + SocksProxyConstants.ESTABLISH_TCPIP_STREAM, + address.getAddress().getHostAddress(), + address.getPort(), + proxyUser); + + } else if (proxyVersion.equalsIgnoreCase("5")) { + req = new SocksProxyRequest( + SocksProxyConstants.SOCKS_VERSION_5, + SocksProxyConstants.ESTABLISH_TCPIP_STREAM, + address, + proxyUser); + + } else { + throw new ConfigError("SOCKS ProxyType must be 4,4a or 5"); + } + + if (proxyPassword != null) { + req.setPassword(proxyPassword); + } + + return req; + } + + public static IoConnector createIoConnector(SocketAddress address) throws ConfigError { if (address instanceof InetSocketAddress) { return new NioSocketConnector(); 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 10e02a5794..deb49a88d3 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/AbstractSocketInitiator.java @@ -104,10 +104,46 @@ protected void createSessionInitiators() sslConfig = SSLSupport.getSslConfig(getSettings(), sessionID); } + String proxyUser = null; + String proxyPassword = null; + String proxyHost = null; + + String proxyType = null; + String proxyVersion = null; + + String proxyWorkstation = null; + String proxyDomain = null; + + int proxyPort = -1; + + if (getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_TYPE)) { + proxyType = settings.getString(sessionID, Initiator.SETTING_PROXY_TYPE); + if (getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_VERSION)) { + proxyVersion = settings.getString(sessionID, + Initiator.SETTING_PROXY_VERSION); + } + + if (getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_USER)) { + proxyUser = settings.getString(sessionID, Initiator.SETTING_PROXY_USER); + proxyPassword = settings.getString(sessionID, + Initiator.SETTING_PROXY_PASSWORD); + } + if (getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_WORKSTATION) + && getSettings().isSetting(sessionID, Initiator.SETTING_PROXY_DOMAIN)) { + proxyWorkstation = settings.getString(sessionID, + Initiator.SETTING_PROXY_WORKSTATION); + proxyDomain = settings.getString(sessionID, Initiator.SETTING_PROXY_DOMAIN); + } + + proxyHost = settings.getString(sessionID, Initiator.SETTING_PROXY_HOST); + proxyPort = (int) settings.getLong(sessionID, Initiator.SETTING_PROXY_PORT); + } + final IoSessionInitiator ioSessionInitiator = new IoSessionInitiator(session, - socketAddresses, localAddress, reconnectingIntervals, getScheduledExecutorService(), - networkingOptions, getEventHandlingStrategy(), getIoFilterChainBuilder(), - sslEnabled, sslConfig); + socketAddresses, localAddress, reconnectingIntervals, + getScheduledExecutorService(), networkingOptions, + getEventHandlingStrategy(), getIoFilterChainBuilder(), sslEnabled, sslConfig, + proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, proxyWorkstation); initiators.add(ioSessionInitiator); } diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java new file mode 100644 index 0000000000..49bbdf620f --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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.core.session.IoSession; +import org.apache.mina.proxy.AbstractProxyIoHandler; + +import quickfix.mina.ssl.SSLFilter; + +class InitiatorProxyIoHandler extends AbstractProxyIoHandler { + private final InitiatorIoHandler initiatorIoHandler; + private final SSLFilter sslFilter; + + InitiatorProxyIoHandler(InitiatorIoHandler initiatorIoHandler, SSLFilter sslFilter) { + super(); + this.initiatorIoHandler = initiatorIoHandler; + this.sslFilter = sslFilter; + } + + @Override + public void sessionCreated(IoSession session) throws Exception { + this.initiatorIoHandler.sessionCreated(session); + } + + @Override + public void sessionClosed(IoSession ioSession) throws Exception { + this.initiatorIoHandler.sessionClosed(ioSession); + } + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + this.initiatorIoHandler.messageReceived(session, message); + } + + @Override + public void messageSent(IoSession session, Object message) throws Exception { + this.initiatorIoHandler.messageSent(session, message); + } + + @Override + public void exceptionCaught(IoSession ioSession, Throwable cause) throws Exception { + this.initiatorIoHandler.exceptionCaught(ioSession, cause); + } + + @Override + public void proxySessionOpened(IoSession ioSession) throws Exception { + if (this.sslFilter != null) { + this.sslFilter.initiateHandshake(ioSession); + } + } +} 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 bf02ed9634..71f1a8bf06 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -24,6 +24,9 @@ import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.logging.LoggingFilter; +import org.apache.mina.proxy.ProxyConnector; +import org.apache.mina.transport.socket.SocketConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import quickfix.ConfigError; @@ -58,22 +61,27 @@ public class IoSessionInitiator { private Future reconnectFuture; protected final static Logger log = LoggerFactory.getLogger("display." + IoSessionInitiator.class.getName()); - public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, SocketAddress localAddress, - int[] reconnectIntervalInSeconds, ScheduledExecutorService executor, - NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, - IoFilterChainBuilder userIoFilterChainBuilder, boolean sslEnabled, SSLConfig sslConfig) throws ConfigError { + public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses, + SocketAddress localAddress, int[] reconnectIntervalInSeconds, + ScheduledExecutorService executor, 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 { this.executor = executor; final long[] reconnectIntervalInMillis = new long[reconnectIntervalInSeconds.length]; for (int ii = 0; ii != reconnectIntervalInSeconds.length; ++ii) { reconnectIntervalInMillis[ii] = reconnectIntervalInSeconds[ii] * 1000L; } try { - reconnectTask = new ConnectTask(sslEnabled, socketAddresses, localAddress, userIoFilterChainBuilder, - fixSession, reconnectIntervalInMillis, networkingOptions, - eventHandlingStrategy, sslConfig); + reconnectTask = new ConnectTask(sslEnabled, socketAddresses, localAddress, + userIoFilterChainBuilder, fixSession, reconnectIntervalInMillis, + networkingOptions, eventHandlingStrategy, sslConfig, + proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, proxyWorkstation); } catch (GeneralSecurityException e) { throw new ConfigError(e); } + log.info("[" + fixSession.getSessionID() + "] " + Arrays.asList(socketAddresses)); } @@ -96,10 +104,22 @@ private static class ConnectTask implements Runnable { private int connectionFailureCount; private ConnectFuture connectFuture; + private final String proxyType; + private final String proxyVersion; + private final String proxyHost; + private final int proxyPort; + private final String proxyUser; + private final String proxyPassword; + private final String proxyDomain; + private final String proxyWorkstation; + public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, - SocketAddress localAddress, IoFilterChainBuilder userIoFilterChainBuilder, Session fixSession, - long[] reconnectIntervalInMillis, NetworkingOptions networkingOptions, - EventHandlingStrategy eventHandlingStrategy, SSLConfig sslConfig) throws ConfigError, GeneralSecurityException { + SocketAddress localAddress, IoFilterChainBuilder userIoFilterChainBuilder, + Session fixSession, long[] reconnectIntervalInMillis, + NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, SSLConfig sslConfig, + String proxyType, String proxyVersion, String proxyHost, + int proxyPort, String proxyUser, String proxyPassword, String proxyDomain, + String proxyWorkstation) throws ConfigError, GeneralSecurityException { this.sslEnabled = sslEnabled; this.socketAddresses = socketAddresses; this.localAddress = localAddress; @@ -109,37 +129,69 @@ public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses, this.networkingOptions = networkingOptions; this.eventHandlingStrategy = eventHandlingStrategy; this.sslConfig = sslConfig; + + this.proxyType = proxyType; + this.proxyVersion = proxyVersion; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPassword = proxyPassword; + this.proxyDomain = proxyDomain; + this.proxyWorkstation = proxyWorkstation; + setupIoConnector(); } private void setupIoConnector() throws ConfigError, GeneralSecurityException { - final IoConnector newConnector = ProtocolFactory.createIoConnector(socketAddresses[0]); final CompositeIoFilterChainBuilder ioFilterChainBuilder = new CompositeIoFilterChainBuilder(userIoFilterChainBuilder); + boolean hasProxy = proxyType != null && proxyPort > 0 && socketAddresses[0] instanceof InetSocketAddress; + + SSLFilter sslFilter = null; if (sslEnabled) { - installSslFilter(ioFilterChainBuilder); + sslFilter = installSslFilter(ioFilterChainBuilder, !hasProxy); } ioFilterChainBuilder.addLast(FIXProtocolCodecFactory.FILTER_NAME, new ProtocolCodecFilter(new FIXProtocolCodecFactory())); - newConnector.setFilterChainBuilder(ioFilterChainBuilder); + IoConnector newConnector; + newConnector = ProtocolFactory.createIoConnector(socketAddresses[0]); newConnector.setHandler(new InitiatorIoHandler(fixSession, networkingOptions, eventHandlingStrategy)); + newConnector.setFilterChainBuilder(ioFilterChainBuilder); + + if (hasProxy) { + ProxyConnector proxyConnector = ProtocolFactory.createIoProxyConnector( + (SocketConnector) newConnector, + (InetSocketAddress) socketAddresses[0], + new InetSocketAddress(proxyHost, proxyPort), + proxyType, proxyVersion, proxyUser, proxyPassword, proxyDomain, proxyWorkstation + ); + + proxyConnector.setHandler(new InitiatorProxyIoHandler( + new InitiatorIoHandler(fixSession, networkingOptions, eventHandlingStrategy), + sslFilter + )); + + newConnector = proxyConnector; + } + if (ioConnector != null) { ioConnector.dispose(); } ioConnector = newConnector; } - private void installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder) + private SSLFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder, boolean autoStart) throws GeneralSecurityException { final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - final SSLFilter sslFilter = new SSLFilter(sslContext); + final SSLFilter sslFilter = new SSLFilter(sslContext, autoStart); sslFilter.setUseClientMode(true); sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() : SSLSupport.getSupportedProtocols(sslContext)); ioFilterChainBuilder.addLast(SSLSupport.FILTER_NAME, sslFilter); + return sslFilter; } public synchronized void run() {