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
+
+
+
+
+
+
+
+
+
+
+