Add support for tunneling TCP/IP connections.

This commit is contained in:
Olli Helenius
2017-07-06 22:32:56 +03:00
parent 5e3a08a637
commit 9e8bef24c5
4 changed files with 142 additions and 8 deletions

View File

@@ -0,0 +1,50 @@
package net.schmizz.sshj.examples;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
import net.schmizz.sshj.connection.channel.direct.Session;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* This example demonstrates connecting via an intermediate "jump" server using a direct TCP/IP channel.
*/
public class Jump {
public static void main(String... args)
throws IOException {
SSHClient firstHop = new SSHClient();
firstHop.loadKnownHosts();
firstHop.connect("localhost");
try {
firstHop.authPublickey(System.getProperty("user.name"));
DirectConnection tunnel = firstHop.newDirectConnection("localhost", 22);
SSHClient ssh = new SSHClient();
try {
ssh.loadKnownHosts();
ssh.connectVia(tunnel);
ssh.authPublickey(System.getProperty("user.name"));
final Session session = ssh.startSession();
try {
final Session.Command cmd = session.exec("ping -c 1 google.com");
System.out.println(IOUtils.readFully(cmd.getInputStream()).toString());
cmd.join(5, TimeUnit.SECONDS);
System.out.println("\n** exit status: " + cmd.getExitStatus());
} finally {
session.close();
}
} finally {
ssh.disconnect();
}
} finally {
firstHop.disconnect();
}
}
}

View File

@@ -23,6 +23,7 @@ import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.connection.Connection; import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException; import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.ConnectionImpl; import net.schmizz.sshj.connection.ConnectionImpl;
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder; import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
import net.schmizz.sshj.connection.channel.direct.Session; import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.SessionChannel; import net.schmizz.sshj.connection.channel.direct.SessionChannel;
@@ -672,6 +673,20 @@ public class SSHClient
return forwarder; return forwarder;
} }
/** Create a {@link DirectConnection} channel that connects to a remote address from the server.
*
* This can be used to open a tunnel to, for example, an HTTP server that is only
* accessible from the SSH server, or opening an SSH connection via a 'jump' server.
*
* @param hostname name of the host to connect to from the server.
* @param port remote port number.
*/
public DirectConnection newDirectConnection(String hostname, int port) throws IOException {
DirectConnection tunnel = new DirectConnection(conn, hostname, port);
tunnel.open();
return tunnel;
}
/** /**
* Register a {@code listener} for handling forwarded X11 channels. Without having done this, an incoming X11 * Register a {@code listener} for handling forwarded X11 channels. Without having done this, an incoming X11
* forwarding will be summarily rejected. * forwarding will be summarily rejected.

View File

@@ -17,6 +17,7 @@ package net.schmizz.sshj;
import com.hierynomus.sshj.backport.JavaVersion; import com.hierynomus.sshj.backport.JavaVersion;
import com.hierynomus.sshj.backport.Jdk7HttpProxySocket; import com.hierynomus.sshj.backport.Jdk7HttpProxySocket;
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import java.io.IOException; import java.io.IOException;
@@ -43,6 +44,9 @@ public abstract class SocketClient {
private int timeout = 0; private int timeout = 0;
private String hostname; private String hostname;
private int port;
private boolean tunneled = false;
SocketClient(int defaultPort) { SocketClient(int defaultPort) {
this.defaultPort = defaultPort; this.defaultPort = defaultPort;
@@ -71,6 +75,7 @@ public abstract class SocketClient {
@Deprecated @Deprecated
public void connect(String hostname, int port, Proxy proxy) throws IOException { public void connect(String hostname, int port, Proxy proxy) throws IOException {
this.hostname = hostname; this.hostname = hostname;
this.port = port;
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) { if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket. // Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
socket = new Jdk7HttpProxySocket(proxy); socket = new Jdk7HttpProxySocket(proxy);
@@ -103,6 +108,7 @@ public abstract class SocketClient {
*/ */
@Deprecated @Deprecated
public void connect(InetAddress host, int port, Proxy proxy) throws IOException { public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
this.port = port;
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) { if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket. // Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
socket = new Jdk7HttpProxySocket(proxy); socket = new Jdk7HttpProxySocket(proxy);
@@ -122,6 +128,7 @@ public abstract class SocketClient {
connect(InetAddress.getByName(null), port); connect(InetAddress.getByName(null), port);
} else { } else {
this.hostname = hostname; this.hostname = hostname;
this.port = port;
socket = socketFactory.createSocket(); socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(hostname, port), connectTimeout); socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
onConnect(); onConnect();
@@ -133,6 +140,7 @@ public abstract class SocketClient {
connect(InetAddress.getByName(null), port, localAddr, localPort); connect(InetAddress.getByName(null), port, localAddr, localPort);
} else { } else {
this.hostname = hostname; this.hostname = hostname;
this.port = port;
socket = socketFactory.createSocket(); socket = socketFactory.createSocket();
socket.bind(new InetSocketAddress(localAddr, localPort)); socket.bind(new InetSocketAddress(localAddr, localPort));
socket.connect(new InetSocketAddress(hostname, port), connectTimeout); socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
@@ -140,11 +148,22 @@ public abstract class SocketClient {
} }
} }
/** Connect to a remote address via a direct TCP/IP connection from the server. */
public void connectVia(DirectConnection directConnection) throws IOException {
this.hostname = directConnection.getRemoteHost();
this.port = directConnection.getRemotePort();
this.input = directConnection.getInputStream();
this.output = directConnection.getOutputStream();
this.tunneled = true;
onConnect();
}
public void connect(InetAddress host) throws IOException { public void connect(InetAddress host) throws IOException {
connect(host, defaultPort); connect(host, defaultPort);
} }
public void connect(InetAddress host, int port) throws IOException { public void connect(InetAddress host, int port) throws IOException {
this.port = port;
socket = socketFactory.createSocket(); socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(host, port), connectTimeout); socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect(); onConnect();
@@ -152,6 +171,7 @@ public abstract class SocketClient {
public void connect(InetAddress host, int port, InetAddress localAddr, int localPort) public void connect(InetAddress host, int port, InetAddress localAddr, int localPort)
throws IOException { throws IOException {
this.port = port;
socket = socketFactory.createSocket(); socket = socketFactory.createSocket();
socket.bind(new InetSocketAddress(localAddr, localPort)); socket.bind(new InetSocketAddress(localAddr, localPort));
socket.connect(new InetSocketAddress(host, port), connectTimeout); socket.connect(new InetSocketAddress(host, port), connectTimeout);
@@ -174,15 +194,15 @@ public abstract class SocketClient {
} }
public boolean isConnected() { public boolean isConnected() {
return (socket != null) && socket.isConnected(); return tunneled || ((socket != null) && socket.isConnected());
} }
public int getLocalPort() { public int getLocalPort() {
return socket.getLocalPort(); return tunneled ? 65536 : socket.getLocalPort();
} }
public InetAddress getLocalAddress() { public InetAddress getLocalAddress() {
return socket.getLocalAddress(); return socket == null ? null : socket.getLocalAddress();
} }
public String getRemoteHostname() { public String getRemoteHostname() {
@@ -190,11 +210,11 @@ public abstract class SocketClient {
} }
public int getRemotePort() { public int getRemotePort() {
return socket.getPort(); return socket == null ? this.port : socket.getPort();
} }
public InetAddress getRemoteAddress() { public InetAddress getRemoteAddress() {
return socket.getInetAddress(); return socket == null ? null : socket.getInetAddress();
} }
public void setSocketFactory(SocketFactory factory) { public void setSocketFactory(SocketFactory factory) {
@@ -238,9 +258,11 @@ public abstract class SocketClient {
} }
void onConnect() throws IOException { void onConnect() throws IOException {
socket.setSoTimeout(timeout); if (socket != null) {
input = socket.getInputStream(); socket.setSoTimeout(timeout);
output = socket.getOutputStream(); input = socket.getInputStream();
output = socket.getOutputStream();
}
} }
} }

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.Connection;
/** A channel for creating a direct TCP/IP connection from the server to a remote address. */
public class DirectConnection extends AbstractDirectChannel {
private final String remoteHost;
private final int remotePort;
public DirectConnection(Connection conn, String remoteHost, int remotePort) {
super(conn, "direct-tcpip");
this.remoteHost = remoteHost;
this.remotePort = remotePort;
}
@Override protected SSHPacket buildOpenReq() {
return super.buildOpenReq()
.putString(getRemoteHost())
.putUInt32(getRemotePort())
.putString("localhost")
.putUInt32(65536); // it looks like OpenSSH uses this value in stdio-forward
}
public String getRemoteHost() {
return remoteHost;
}
public int getRemotePort() {
return remotePort;
}
}