[SSHD-832] Allow users to specify a local socket address when creating an SSH client connection
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f1cc8509 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f1cc8509 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f1cc8509 Branch: refs/heads/master Commit: f1cc85099aab8f8d9e09c054bcbbeea18fd669b1 Parents: 20dedf3 Author: Lyor Goldstein <[email protected]> Authored: Mon Jul 9 21:11:41 2018 +0300 Committer: Goldstein Lyor <[email protected]> Committed: Tue Jul 10 07:24:51 2018 +0300 ---------------------------------------------------------------------- .../java/org/apache/sshd/client/SshClient.java | 41 ++++++++------- .../client/session/ClientSessionCreator.java | 52 ++++++++++++++++++-- .../AbstractSimpleClientSessionCreator.java | 19 ++++++- .../org/apache/sshd/common/AttributeStore.java | 4 +- .../apache/sshd/common/io/IoConnectFuture.java | 2 +- .../org/apache/sshd/common/io/IoSession.java | 11 ++--- .../apache/sshd/common/io/IoWriteFuture.java | 2 +- 7 files changed, 99 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java index 733304f..143e4d7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java @@ -131,7 +131,6 @@ import org.apache.sshd.common.util.net.SshdSocketAddress; * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public class SshClient extends AbstractFactoryManager implements ClientFactoryManager, ClientSessionCreator, Closeable { - public static final Factory<SshClient> DEFAULT_SSH_CLIENT_FACTORY = SshClient::new; /** @@ -433,7 +432,9 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } @Override - public ConnectFuture connect(String username, String host, int port) throws IOException { + public ConnectFuture connect( + String username, String host, int port, SocketAddress localAddress) + throws IOException { HostConfigEntryResolver resolver = getHostConfigEntryResolver(); HostConfigEntry entry = resolver.resolveEffectiveHost(host, port, username); if (entry == null) { @@ -455,14 +456,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } } - return connect(entry); + return connect(entry, localAddress); } @Override - public ConnectFuture connect(String username, SocketAddress address) throws IOException { - Objects.requireNonNull(address, "No target address"); - if (address instanceof InetSocketAddress) { - InetSocketAddress inetAddress = (InetSocketAddress) address; + public ConnectFuture connect( + String username, SocketAddress targetAddress, SocketAddress localAddress) + throws IOException { + Objects.requireNonNull(targetAddress, "No target address"); + if (targetAddress instanceof InetSocketAddress) { + InetSocketAddress inetAddress = (InetSocketAddress) targetAddress; String host = ValidateUtils.checkNotNullAndNotEmpty(inetAddress.getHostString(), "No host"); int port = inetAddress.getPort(); ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); @@ -474,7 +477,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa log.debug("connect({}@{}:{}) no overrides", username, host, port); } - return doConnect(username, address, Collections.emptyList(), true); + return doConnect(username, targetAddress, localAddress, Collections.emptyList(), true); } else { if (log.isDebugEnabled()) { log.debug("connect({}@{}:{}) effective: {}", username, host, port, entry); @@ -484,21 +487,22 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } } else { if (log.isDebugEnabled()) { - log.debug("connect({}@{}) not an InetSocketAddress: {}", username, address, address.getClass().getName()); + log.debug("connect({}@{}) not an InetSocketAddress: {}", username, targetAddress, targetAddress.getClass().getName()); } - return doConnect(username, address, Collections.emptyList(), true); + return doConnect(username, targetAddress, localAddress, Collections.emptyList(), true); } } @Override - public ConnectFuture connect(HostConfigEntry hostConfig) throws IOException { + public ConnectFuture connect(HostConfigEntry hostConfig, SocketAddress localAddress) throws IOException { Objects.requireNonNull(hostConfig, "No host configuration"); String host = ValidateUtils.checkNotNullAndNotEmpty(hostConfig.getHostName(), "No target host"); int port = hostConfig.getPort(); ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); Collection<KeyPair> keys = loadClientIdentities(hostConfig.getIdentities(), IoUtils.EMPTY_LINK_OPTIONS); - return doConnect(hostConfig.getUsername(), new InetSocketAddress(host, port), keys, !hostConfig.isIdentitiesOnly()); + return doConnect(hostConfig.getUsername(), new InetSocketAddress(host, port), + localAddress, keys, !hostConfig.isIdentitiesOnly()); } protected List<KeyPair> loadClientIdentities(Collection<String> locations, LinkOption... options) throws IOException { @@ -544,16 +548,17 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } protected ConnectFuture doConnect( - String username, SocketAddress address, Collection<? extends KeyPair> identities, boolean useDefaultIdentities) - throws IOException { + String username, SocketAddress targetAddress, SocketAddress localAddress, + Collection<? extends KeyPair> identities, boolean useDefaultIdentities) + throws IOException { if (connector == null) { throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server"); } - ConnectFuture connectFuture = new DefaultConnectFuture(username + "@" + address, null); + ConnectFuture connectFuture = new DefaultConnectFuture(username + "@" + targetAddress, null); SshFutureListener<IoConnectFuture> listener = - createConnectCompletionListener(connectFuture, username, address, identities, useDefaultIdentities); - connector.connect(address, null).addListener(listener); + createConnectCompletionListener(connectFuture, username, targetAddress, identities, useDefaultIdentities); + connector.connect(targetAddress, localAddress).addListener(listener); return connectFuture; } @@ -573,7 +578,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa if (t != null) { if (log.isDebugEnabled()) { log.debug("operationComplete({}@{}) failed ({}): {}", - username, address, t.getClass().getSimpleName(), t.getMessage()); + username, address, t.getClass().getSimpleName(), t.getMessage()); } connectFuture.setException(t); } else { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionCreator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionCreator.java index c3d46e5..4d0a7e8 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionCreator.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionCreator.java @@ -39,7 +39,24 @@ public interface ClientSessionCreator { * connect to it * @see #connect(HostConfigEntry) */ - ConnectFuture connect(String username, String host, int port) throws IOException; + default ConnectFuture connect(String username, String host, int port) throws IOException { + return connect(username, host, port, null); + } + + /** + * Resolves the <U>effective</U> {@link HostConfigEntry} and connects to it + * + * @param username The intended username + * @param host The target host name/address - never {@code null}/empty + * @param port The target port + * @param localAddress The local address to use - if {@code null} an + * automatic ephemeral port and bind address is used + * @return A {@link ConnectFuture} + * @throws IOException If failed to resolve the effective target or + * connect to it + * @see #connect(HostConfigEntry) + */ + ConnectFuture connect(String username, String host, int port, SocketAddress localAddress) throws IOException; /** * Resolves the <U>effective</U> {@link HostConfigEntry} and connects to it @@ -53,12 +70,41 @@ public interface ClientSessionCreator { * connect to it * @see #connect(HostConfigEntry) */ - ConnectFuture connect(String username, SocketAddress address) throws IOException; + default ConnectFuture connect(String username, SocketAddress address) throws IOException { + return connect(username, address, null); + } + + /** + * Resolves the <U>effective</U> {@link HostConfigEntry} and connects to it + * + * @param username The intended username + * @param targetAddress The intended target {@link SocketAddress} - never {@code null}. + * If this is an {@link java.net.InetSocketAddress} then the <U>effective</U> + * {@link HostConfigEntry} is resolved and used. + * @param localAddress The local address to use - if {@code null} an + * automatic ephemeral port and bind address is used + * @return A {@link ConnectFuture} + * @throws IOException If failed to resolve the effective target or + * connect to it + * @see #connect(HostConfigEntry) + */ + ConnectFuture connect(String username, SocketAddress targetAddress, SocketAddress localAddress) throws IOException; + + /** + * @param hostConfig The effective {@link HostConfigEntry} to connect to - never {@code null} + * @return A {@link ConnectFuture} + * @throws IOException If failed to create the connection future + */ + default ConnectFuture connect(HostConfigEntry hostConfig) throws IOException { + return connect(hostConfig, null); + } /** * @param hostConfig The effective {@link HostConfigEntry} to connect to - never {@code null} + * @param localAddress The local address to use - if {@code null} an + * automatic ephemeral port and bind address is used * @return A {@link ConnectFuture} * @throws IOException If failed to create the connection future */ - ConnectFuture connect(HostConfigEntry hostConfig) throws IOException; + ConnectFuture connect(HostConfigEntry hostConfig, SocketAddress localAddress) throws IOException; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java index b929c6f..3dd904f 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java @@ -136,7 +136,7 @@ public abstract class AbstractSimpleClientSessionCreator extends AbstractSimpleC * @return The {@link SimpleClient} wrapper. <B>Note:</B> closing the wrapper * also closes the underlying sessions creator. */ - public static SimpleClient wrap(final ClientSessionCreator creator, final Channel channel) { + public static SimpleClient wrap(ClientSessionCreator creator, Channel channel) { Objects.requireNonNull(creator, "No sessions creator"); Objects.requireNonNull(channel, "No channel"); return new AbstractSimpleClientSessionCreator() { @@ -146,16 +146,33 @@ public abstract class AbstractSimpleClientSessionCreator extends AbstractSimpleC } @Override + public ConnectFuture connect(String username, String host, int port, SocketAddress localAddress) + throws IOException { + return creator.connect(username, host, port, localAddress); + } + + @Override public ConnectFuture connect(String username, SocketAddress address) throws IOException { return creator.connect(username, address); } @Override + public ConnectFuture connect(String username, SocketAddress targetAddress, SocketAddress localAddress) + throws IOException { + return creator.connect(username, targetAddress, localAddress); + } + + @Override public ConnectFuture connect(HostConfigEntry hostConfig) throws IOException { return creator.connect(hostConfig); } @Override + public ConnectFuture connect(HostConfigEntry hostConfig, SocketAddress localAddress) throws IOException { + return creator.connect(hostConfig, localAddress); + } + + @Override public boolean isOpen() { return channel.isOpen(); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/common/AttributeStore.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/AttributeStore.java b/sshd-core/src/main/java/org/apache/sshd/common/AttributeStore.java index 4907efa..2b58d94 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/AttributeStore.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/AttributeStore.java @@ -66,7 +66,7 @@ public interface AttributeStore { * * @param <T> The generic attribute type * @param key The key of the attribute; must not be {@code null}. - * @return <tt>null</tt> if there is no value associated with the specified key + * @return {@code null} if there is no value associated with the specified key */ <T> T getAttribute(AttributeKey<T> key); @@ -95,7 +95,7 @@ public interface AttributeStore { * * @param <T> The generic attribute type * @param key The key of the attribute; must not be {@code null}. - * @return <tt>null</tt> if there is no value associated with the specified key + * @return {@code null} if there is no value associated with the specified key */ <T> T resolveAttribute(AttributeKey<T> key); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/common/io/IoConnectFuture.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/IoConnectFuture.java b/sshd-core/src/main/java/org/apache/sshd/common/io/IoConnectFuture.java index 91f2a90..0f946b3 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/io/IoConnectFuture.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/io/IoConnectFuture.java @@ -32,7 +32,7 @@ public interface IoConnectFuture extends SshFuture<IoConnectFuture> { /** * Returns the cause of the connection failure. * - * @return <tt>null</tt> if the connect operation is not finished yet, + * @return {@code null} if the connect operation is not finished yet, * or if the connection attempt is successful. * @see #getSession() */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java b/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java index 1c629bd..489eda7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java @@ -34,7 +34,7 @@ public interface IoSession extends PacketWriter, Closeable { * Returns the value of the user-defined attribute of this session. * * @param key the key of the attribute - * @return <tt>null</tt> if there is no attribute with the specified key + * @return {@code null} if there is no attribute with the specified key */ Object getAttribute(Object key); @@ -43,7 +43,7 @@ public interface IoSession extends PacketWriter, Closeable { * * @param key the key of the attribute * @param value the value of the attribute - * @return The old value of the attribute. <tt>null</tt> if it is new. + * @return The old value of the attribute. {@code null} if it is new. */ Object setAttribute(Object key, Object value); @@ -61,7 +61,7 @@ public interface IoSession extends PacketWriter, Closeable { * * @param key The key of the attribute we want to set * @param value The value we want to set - * @return The old value of the attribute. <tt>null</tt> if not found. + * @return The old value of the attribute. {@code null} if not found. */ Object setAttributeIfAbsent(Object key, Object value); @@ -69,7 +69,7 @@ public interface IoSession extends PacketWriter, Closeable { * Removes a user-defined attribute with the specified key. * * @param key The key of the attribute we want to remove - * @return The old value of the attribute - <tt>null</tt> if not found. + * @return The old value of the attribute - {@code null} if not found. */ Object removeAttribute(Object key); @@ -79,8 +79,7 @@ public interface IoSession extends PacketWriter, Closeable { SocketAddress getRemoteAddress(); /** - * @return the socket address of local machine which is associated with this - * session. + * @return the socket address of local machine which is associated with this session. */ SocketAddress getLocalAddress(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f1cc8509/sshd-core/src/main/java/org/apache/sshd/common/io/IoWriteFuture.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/IoWriteFuture.java b/sshd-core/src/main/java/org/apache/sshd/common/io/IoWriteFuture.java index 95d33bd..543f285 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/io/IoWriteFuture.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/io/IoWriteFuture.java @@ -30,7 +30,7 @@ public interface IoWriteFuture extends SshFuture<IoWriteFuture>, VerifiableFutur /** * @return the cause of the write failure if and only if the write * operation has failed due to an {@link Exception}. Otherwise, - * <tt>null</tt> is returned (use {@link #isDone()} to distinguish + * {@code null} is returned (use {@link #isDone()} to distinguish * between the two. */ Throwable getException();
