This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit fb0b0c4dfac7df2f5a4102d87397e12171615fbb
Author: ggrandes <guillermo.gran...@gmail.com>
AuthorDate: Tue Jul 14 00:12:40 2020 +0200

    [SSHD-1033]: Fix simultaneous usage of dynamic and local port forwarding
    
    # Conflicts:
    #   CHANGES.md
---
 CHANGES.md                                         |   2 +
 docs/changes/2.6.0.md                              |   6 +
 .../common/forward/DefaultForwardingFilter.java    |  62 +++++----
 .../apache/sshd/common/forward/Sshd1033Test.java   | 151 +++++++++++++++++++++
 4 files changed, 198 insertions(+), 23 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index f9b463f..c51a3ca 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,6 +8,8 @@
 
 # [Version 2.5.0 to 2.5.1](./docs/changes/2.5.1.md)
 
+# [Version 2.5.1 to 2.6.0](./docs/changes/2.6.0.md)
+
 # Planned for next version
 
 ## Major code re-factoring
diff --git a/docs/changes/2.6.0.md b/docs/changes/2.6.0.md
new file mode 100644
index 0000000..a0c4420
--- /dev/null
+++ b/docs/changes/2.6.0.md
@@ -0,0 +1,6 @@
+# Introduced in version 2.6.0
+
+## Behavioral changes and enhancements
+
+* [SSHD-1033](https://issues.apache.org/jira/browse/SSHD-1033) Fix 
simultaneous usage of dynamic and local port forwarding.
+
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwardingFilter.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwardingFilter.java
index 5c05b81..28dbff0 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwardingFilter.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwardingFilter.java
@@ -111,7 +111,8 @@ public class DefaultForwardingFilter
     private final Collection<PortForwardingEventListenerManager> 
managersHolder = new CopyOnWriteArraySet<>();
     private final PortForwardingEventListener listenerProxy;
 
-    private IoAcceptor acceptor;
+    private IoAcceptor localAcceptor; 
+    private IoAcceptor dynamicAcceptor;
 
     public DefaultForwardingFilter(ConnectionService service) {
         this.service = Objects.requireNonNull(service, "No connection 
service");
@@ -200,7 +201,7 @@ public class DefaultForwardingFilter
         int port;
         signalEstablishingExplicitTunnel(local, remote, true);
         try {
-            bound = doBind(local, staticIoHandlerFactory);
+            bound = doBind(local, getLocalIoAcceptor(staticIoHandlerFactory));
             port = bound.getPort();
             synchronized (localLock) {
                 SshdSocketAddress prevRemote = localToRemote.get(port);
@@ -261,7 +262,7 @@ public class DefaultForwardingFilter
     protected void unbindLocalForwarding(
             SshdSocketAddress local, SshdSocketAddress remote, 
InetSocketAddress bound)
             throws IOException {
-        if ((bound != null) && (acceptor != null)) {
+        if ((bound != null) && (localAcceptor != null)) {
             if (log.isDebugEnabled()) {
                 log.debug("unbindLocalForwarding({} => {}) unbind {}", local, 
remote, bound);
             }
@@ -271,7 +272,7 @@ public class DefaultForwardingFilter
                 signalTearingDownExplicitTunnel(boundAddress, true, remote);
             } finally {
                 try {
-                    acceptor.unbind(bound);
+                    localAcceptor.unbind(bound);
                 } catch (RuntimeException e) {
                     signalTornDownExplicitTunnel(boundAddress, true, remote, 
e);
                     throw e;
@@ -282,7 +283,7 @@ public class DefaultForwardingFilter
         } else {
             if (log.isDebugEnabled()) {
                 log.debug("unbindLocalForwarding({} => {}) no mapping({}) or 
acceptor({})",
-                        local, remote, bound, acceptor);
+                        local, remote, bound, localAcceptor);
             }
         }
     }
@@ -470,7 +471,7 @@ public class DefaultForwardingFilter
         int port;
         signalEstablishingDynamicTunnel(local);
         try {
-            bound = doBind(local, socksProxyIoHandlerFactory);
+            bound = doBind(local, 
getDynamicIoAcceptor(socksProxyIoHandlerFactory));
             port = bound.getPort();
             synchronized (dynamicLock) {
                 SocksProxy prevProxy = dynamicLocal.get(port);
@@ -614,15 +615,15 @@ public class DefaultForwardingFilter
                             proxy.close(true);
                         }
                     } finally {
-                        if ((bound != null) && (acceptor != null)) {
+                        if ((bound != null) && (dynamicAcceptor != null)) {
                             if (debugEnabled) {
                                 log.debug("stopDynamicPortForwarding({}) 
unbind address={}", local, bound);
                             }
-                            acceptor.unbind(bound);
+                            dynamicAcceptor.unbind(bound);
                         } else {
                             if (debugEnabled) {
                                 log.debug("stopDynamicPortForwarding({}) no 
acceptor({}) or no binding({})",
-                                        local, acceptor, bound);
+                                        local, dynamicAcceptor, bound);
                             }
                         }
                     }
@@ -738,7 +739,7 @@ public class DefaultForwardingFilter
         signalEstablishingExplicitTunnel(local, null, true);
         SshdSocketAddress result;
         try {
-            InetSocketAddress bound = doBind(local, staticIoHandlerFactory);
+            InetSocketAddress bound = doBind(local, 
getLocalIoAcceptor(staticIoHandlerFactory));
             result = new SshdSocketAddress(bound.getHostString(), 
bound.getPort());
             if (log.isDebugEnabled()) {
                 log.debug("localPortForwardingRequested(" + local + "): " + 
result);
@@ -782,14 +783,14 @@ public class DefaultForwardingFilter
             }
         }
 
-        if ((entry != null) && (acceptor != null)) {
+        if ((entry != null) && (localAcceptor != null)) {
             if (log.isDebugEnabled()) {
                 log.debug("localPortForwardingCancelled(" + local + ") unbind 
" + entry);
             }
 
             signalTearingDownExplicitTunnel(entry, true, null);
             try {
-                acceptor.unbind(entry.toInetSocketAddress());
+                localAcceptor.unbind(entry.toInetSocketAddress());
             } catch (RuntimeException e) {
                 signalTornDownExplicitTunnel(entry, true, null, e);
                 throw e;
@@ -964,7 +965,8 @@ public class DefaultForwardingFilter
 
     @Override
     protected synchronized Closeable getInnerCloseable() {
-        return builder().parallel(toString(), 
dynamicLocal.values()).close(acceptor).build();
+               return builder().parallel(toString(), dynamicLocal.values())
+                .close(localAcceptor).close(dynamicAcceptor).build();
     }
 
     @Override
@@ -974,22 +976,36 @@ public class DefaultForwardingFilter
         super.preClose();
     }
 
+       protected IoAcceptor createIoAcceptor(Factory<? extends IoHandler> 
handlerFactory) {
+               Session session = getSession();
+               FactoryManager manager = 
Objects.requireNonNull(session.getFactoryManager(), "No factory manager");
+               IoServiceFactory factory = 
Objects.requireNonNull(manager.getIoServiceFactory(), "No I/O service factory");
+               IoHandler handler = handlerFactory.create();
+               return factory.createAcceptor(handler);
+       }
+
+       protected IoAcceptor getLocalIoAcceptor(Factory<? extends IoHandler> 
handlerFactory) {
+               if (localAcceptor == null) {
+                       localAcceptor = createIoAcceptor(handlerFactory);
+               }
+               return localAcceptor;
+       }
+
+       protected IoAcceptor getDynamicIoAcceptor(Factory<? extends IoHandler> 
handlerFactory) {
+               if (dynamicAcceptor == null) {
+                       dynamicAcceptor = createIoAcceptor(handlerFactory);
+               }
+               return dynamicAcceptor;
+       }
+    
     /**
      * @param  address        The request bind address
-     * @param  handlerFactory A {@link Factory} to create an {@link IoHandler} 
if necessary
+     * @param  acceptor       An {@link IoAcceptor} to bind addresses
      * @return                The {@link InetSocketAddress} to which the 
binding occurred
      * @throws IOException    If failed to bind
      */
-    private InetSocketAddress doBind(SshdSocketAddress address, Factory<? 
extends IoHandler> handlerFactory)
+    protected InetSocketAddress doBind(SshdSocketAddress address, IoAcceptor 
acceptor)
             throws IOException {
-        if (acceptor == null) {
-            Session session = getSession();
-            FactoryManager manager = 
Objects.requireNonNull(session.getFactoryManager(), "No factory manager");
-            IoServiceFactory factory = 
Objects.requireNonNull(manager.getIoServiceFactory(), "No I/O service factory");
-            IoHandler handler = handlerFactory.create();
-            acceptor = factory.createAcceptor(handler);
-        }
-
         // TODO find a better way to determine the resulting bind address - 
what if multi-threaded calls...
         Set<SocketAddress> before = acceptor.getBoundAddresses();
         try {
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/forward/Sshd1033Test.java 
b/sshd-core/src/test/java/org/apache/sshd/common/forward/Sshd1033Test.java
new file mode 100644
index 0000000..26e0259
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/forward/Sshd1033Test.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.common.forward;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.forward.DynamicPortForwardingTracker;
+import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.channel.ChannelSessionFactory;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.forward.DirectTcpipFactory;
+import org.apache.sshd.server.forward.ForwardedTcpipFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Local + dynamic port forwarding test
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class Sshd1033Test extends BaseTestSupport {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(Sshd1033Test.class);
+
+    private static SshServer sshd;
+    private static int sshPort;
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        sshd = CoreTestSupportUtils.setupTestServer(Sshd1033Test.class);
+        sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+        sshd.setChannelFactories(Arrays.asList(
+                ChannelSessionFactory.INSTANCE,
+                DirectTcpipFactory.INSTANCE,
+                ForwardedTcpipFactory.INSTANCE));
+        sshd.start();
+        sshPort = sshd.getPort();
+    }
+
+    @AfterClass
+    public static void afterClass() throws Exception {
+        sshd.stop();
+    }
+
+    @Test
+    public void testDirect() throws IOException {
+        testRemoteURL(null);
+    }
+
+    @Test
+    public void testLocalAndDynamic() throws IOException {
+        doTest(true, true);
+    }
+
+    @Test
+    public void testLocal() throws IOException {
+        doTest(true, false);
+    }
+
+    @Test
+    public void testDynamic() throws IOException {
+        doTest(false, true);
+    }
+
+    protected void doTest(boolean testLocal, boolean testDynamic) throws 
IOException {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);
+            client.start();
+
+            try (ClientSession session = client.connect("temp", "localhost", 
sshPort).verify().getClientSession()) {
+                session.addPasswordIdentity("temp");
+                session.auth().verify();
+
+                if (testLocal) {
+                    LOGGER.info("================== Local ==================");
+                    try (ExplicitPortForwardingTracker localTracker = 
session.createLocalPortForwardingTracker(
+                            new SshdSocketAddress("localhost", 8082),
+                            new SshdSocketAddress("test.javastack.org", 80))) {
+                        LOGGER.info("LocalPortForwarding: {} -> {}", 
localTracker.getLocalAddress(), localTracker.getRemoteAddress());
+                        SshdSocketAddress localSocketAddress = 
localTracker.getLocalAddress();
+                        assertNotNull(localSocketAddress);
+                        Proxy proxy = new Proxy(Proxy.Type.HTTP,
+                                new 
InetSocketAddress(localSocketAddress.getHostName(), 
localSocketAddress.getPort()));
+                        testRemoteURL(proxy);
+                    }
+                }
+
+                if (testDynamic) {
+                    LOGGER.info("================== Dynamic 
==================");
+                    try (DynamicPortForwardingTracker dynamicTracker = 
session.createDynamicPortForwardingTracker(
+                            new SshdSocketAddress("localhost", 8000))) {
+                        LOGGER.info("DynamicPortForwarding: {}", 
dynamicTracker.getLocalAddress());
+                        SshdSocketAddress dynamicSocketAddress = 
dynamicTracker.getLocalAddress();
+                        assertNotNull(dynamicSocketAddress);
+                        Proxy proxy = new Proxy(Proxy.Type.SOCKS,
+                                new 
InetSocketAddress(dynamicSocketAddress.getHostName(), //
+                                        dynamicSocketAddress.getPort()));
+                        testRemoteURL(proxy);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void testRemoteURL(final Proxy proxy) throws IOException {
+        URL url = new URL("http://test.javastack.org/";);
+        HttpURLConnection connection = (HttpURLConnection) (proxy != null ? 
url.openConnection(proxy) : url.openConnection());
+        LOGGER.info("Get URL: {}", connection.getURL());
+        try (BufferedReader in = new BufferedReader(new 
InputStreamReader(connection.getInputStream()))) {
+            String result = in.lines().collect(Collectors.joining("\n"));
+            LOGGER.info("Response from server: {}", result);
+            assertEquals("OK", result);
+        }
+    }
+
+}

Reply via email to