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); + } + } + +}