diff --git a/build.gradle b/build.gradle index d089fca6..dc9be4a6 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,8 @@ dependencies { testCompile "org.mockito:mockito-core:1.9.5" testCompile "org.apache.sshd:sshd-core:1.0.0" testRuntime "ch.qos.logback:logback-classic:1.1.2" + testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17' + } jar { diff --git a/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java b/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java index 7716ce65..c942757b 100644 --- a/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java +++ b/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java @@ -216,13 +216,15 @@ public class ConnectionImpl throws ConnectionException { synchronized (globalReqPromises) { Promise gr = globalReqPromises.poll(); - if (gr == null) + if (gr == null) { throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, - "Got a global request response when none was requested"); - else if (response == null) + "Got a global request response when none was requested"); + } else if (response == null) { gr.deliverError(new ConnectionException("Global request [" + gr + "] failed")); - else - gr.deliver(response); + } else { + // To prevent a race condition, copy the packet before delivering, as it will be handled in a different thread. + gr.deliver(new SSHPacket(response)); + } } } @@ -278,4 +280,4 @@ public class ConnectionImpl return keepAlive; } -} \ No newline at end of file +} diff --git a/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java b/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java new file mode 100644 index 00000000..579f06a9 --- /dev/null +++ b/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java @@ -0,0 +1,44 @@ +package com.hierynomus.sshj.connection.channel.forwarded; + +import com.hierynomus.sshj.test.HttpServer; +import com.hierynomus.sshj.test.SshFixture; +import com.hierynomus.sshj.test.util.FileUtil; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder; +import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener; +import org.apache.sshd.server.forward.AcceptAllForwardingFilter; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +import static org.junit.Assert.*; + +public class RemotePortForwarderTest { + + @Rule + public SshFixture fixture = new SshFixture(); + + @Rule + public HttpServer httpServer = new HttpServer(); + + @Test + public void shouldDynamicallyForwardPort() throws IOException { + fixture.getServer().setTcpipForwardingFilter(new AcceptAllForwardingFilter()); + File file = httpServer.getDocRoot().newFile("index.html"); + FileUtil.writeToFile(file, "

Hi!

"); + SSHClient sshClient = fixture.setupConnectedDefaultClient(); + sshClient.authPassword("jeroen", "jeroen"); + sshClient.getRemotePortForwarder().bind( + // where the server should listen + new RemotePortForwarder.Forward(0), + // what we do with incoming connections that are forwarded to us + new SocketForwardingConnectListener(new InetSocketAddress("127.0.0.1", 8080))); + + } +} diff --git a/src/test/java/com/hierynomus/sshj/test/HttpServer.java b/src/test/java/com/hierynomus/sshj/test/HttpServer.java new file mode 100644 index 00000000..9f6044fa --- /dev/null +++ b/src/test/java/com/hierynomus/sshj/test/HttpServer.java @@ -0,0 +1,45 @@ +package com.hierynomus.sshj.test; + +import org.junit.rules.ExternalResource; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +/** + * Can be used to setup a test HTTP server + */ +public class HttpServer extends ExternalResource { + + private org.glassfish.grizzly.http.server.HttpServer httpServer; + + private TemporaryFolder docRoot = new TemporaryFolder(); + + public HttpServer() { + } + + @Override + protected void before() throws Throwable { + docRoot.create(); + httpServer = org.glassfish.grizzly.http.server.HttpServer.createSimpleServer(docRoot.getRoot().getAbsolutePath()); + httpServer.start(); + } + + @Override + protected void after() { + try { + httpServer.shutdownNow(); + } catch (Exception e) {} + try { + docRoot.delete(); + } catch (Exception e) {} + + } + + public org.glassfish.grizzly.http.server.HttpServer getHttpServer() { + return httpServer; + } + + public TemporaryFolder getDocRoot() { + return docRoot; + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 00000000..ac8ace3c --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%.-20thread] %-5level %logger{36} - %msg%n + + + + + + + + + + +