Skip to content

Commit cdd5910

Browse files
committed
SSHD-1269: Fix TCP/IP remote port forwarding with wildcard addresses
Remote port forwarding using an OpenSSH client and an Apache MINA sshd server didn't work with wildcard addresses or with "localhost". For instance, ssh ... -R 0.0.0.0:0:somewhere:1234 would fail: the Apache MINA sshd server would send a forwarded-tcpip request with 127.0.0.1:55555 (if port 55555 was chosen) to OpenSSH, but OpenSSH expected 0.0.0.0:55555. ssh ... -R 0:somewhere:1234 also failed in the same way. Fix this by sending back in the forwarded-tcpip request the original address with the bound port. (Note: this was already fixed by SSHD-792 as of Apache MINA sshd 2.2.0, but was broken again in 2.6.0. Unit tests using an Apache MINA sshd client to initiate the remote port forwarding didn't detect the problem because Apache MINA sshd only checks the port but not the hostname.)
1 parent 9026fe6 commit cdd5910

File tree

5 files changed

+42
-10
lines changed

5 files changed

+42
-10
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,4 @@ Was originally in *HostConfigEntry*.
107107
* [SSHD-1262](https://issues.apache.org/jira/browse/SSHD-1262) TCP/IP port forwarding: don't buffer, and don't read from port before channel is open
108108
* [SSHD-1264](https://issues.apache.org/jira/browse/SSHD-1264) Create KEX negotiation proposal only once per session, not on every re-KEX
109109
* [SSHD-1266](https://issues.apache.org/jira/browse/SSHD-1266) Fix encoding/decoding critical options in OpenSSH certificates
110+
* [SSHD-1269](https://issues.apache.org/jira/browse/SSHD-1269) Fix TCP/IP remote port forwarding with wildcard addresses

sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,13 @@ public Type getTcpipChannelType() {
9393

9494
public void updateLocalForwardingEntry(LocalForwardingEntry entry) {
9595
Objects.requireNonNull(entry, "No local forwarding entry provided");
96-
localEntry = entry.getBoundAddress();
96+
// OpenSSH requires the host string it passed in the tcpip-forward global request: it compares both host and
97+
// port and refuses the forwarding request when it doesn't match in the forwarded-tcpip request.
98+
//
99+
// Note: Apache MINA sshd is currently more lenient in that respect and compares only the port. RFC 4254
100+
// states that "Implementations MUST reject these messages unless they have previously requested a remote TCP/IP
101+
// port forwarding with the given port number"; it does not require the host name to be checked.
102+
localEntry = new SshdSocketAddress(entry.getLocalAddress().getHostName(), entry.getBoundAddress().getPort());
97103
}
98104

99105
@Override

sshd-core/src/test/java/org/apache/sshd/common/forward/Sshd1055Test.java renamed to sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingWithOpenSshTest.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import org.junit.Test;
4949
import org.junit.experimental.categories.Category;
5050
import org.junit.rules.TemporaryFolder;
51+
import org.junit.runner.RunWith;
52+
import org.junit.runners.Parameterized;
5153
import org.slf4j.Logger;
5254
import org.slf4j.LoggerFactory;
5355
import org.testcontainers.Testcontainers;
@@ -68,11 +70,20 @@
6870
* half-closing the socket (shutting down its output), otherwise the client connected to the forwarded port on localhost
6971
* will hang.
7072
* </p>
73+
* <p>
74+
* The test is re-run with different ways to specify the remote port forwarding, including wildcard addresses. The port
75+
* is always zero, letting the server choose any unused port. With a fixed port number, the test might fail if that
76+
* fixed port was already used in the CI environment.
77+
* </p>
78+
*
79+
* @see <a href="https://issues.apache.org/jira/browse/SSHD-1055">SSHD-1055</a>
80+
* @see <a href="https://issues.apache.org/jira/browse/SSHD-1269">SSHD-1269</a>
7181
*/
82+
@RunWith(Parameterized.class)
7283
@Category(ContainerTestCase.class)
73-
public class Sshd1055Test extends BaseTestSupport {
84+
public class PortForwardingWithOpenSshTest extends BaseTestSupport {
7485

75-
private static final Logger LOG = LoggerFactory.getLogger(Sshd1055Test.class);
86+
private static final Logger LOG = LoggerFactory.getLogger(PortForwardingWithOpenSshTest.class);
7687

7788
// We re-use a key from the ClientOpenSSHCertificatesTest.
7889
private static final String TEST_KEYS = "org/apache/sshd/client/opensshcerts/user";
@@ -89,8 +100,21 @@ public class Sshd1055Test extends BaseTestSupport {
89100
private CountDownLatch forwardingSetup;
90101
private int forwardedPort;
91102

92-
public Sshd1055Test() {
93-
super();
103+
private final String portToForward;
104+
105+
public PortForwardingWithOpenSshTest(String portToForward) {
106+
this.portToForward = portToForward;
107+
}
108+
109+
/**
110+
* Uses different ways to specify the remote port forwarding.
111+
*
112+
* @return the remote port specifications to use
113+
* @see <a href="https://issues.apache.org/jira/browse/SSHD-1269">SSHD-1269</a>
114+
*/
115+
@Parameterized.Parameters(name = "{0}")
116+
public static String[] portSpecifications() {
117+
return new String[] { "127.0.0.1:0", "0.0.0.0:0", "0", "localhost:0" };
94118
}
95119

96120
@Before
@@ -113,7 +137,7 @@ public void startServers() throws Exception {
113137
LOG.info("gRPC running on port {}", gRpcPort);
114138
// sshd server
115139
forwardingSetup = new CountDownLatch(1);
116-
sshd = CoreTestSupportUtils.setupTestServer(Sshd1055Test.class);
140+
sshd = CoreTestSupportUtils.setupTestServer(PortForwardingWithOpenSshTest.class);
117141
sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
118142
sshd.setForwarderFactory(new DefaultForwarderFactory() {
119143
@Override
@@ -148,13 +172,14 @@ public void teardownServers() throws Exception {
148172

149173
@Test
150174
public void forwardingWithConnectionClose() throws Exception {
151-
// Write the entrypoint file
175+
// Write the entrypoint file. From within the test container, the host running the container and our two servers
176+
// is accessible as "host.testcontainers.internal".
152177
File entryPoint = tmp.newFile();
153178
String lines = "#!/bin/sh\n" //
154179
+ "\n" //
155180
+ "chmod 0600 /root/.ssh/*\n" //
156181
+ "/usr/bin/ssh -o 'ExitOnForwardFailure yes' -o 'StrictHostKeyChecking off' -vvv -p " + sshPort //
157-
+ " -x -N -T -R 127.0.0.1:0:host.testcontainers.internal:" + gRpcPort //
182+
+ " -x -N -T -R " + portToForward + ":host.testcontainers.internal:" + gRpcPort //
158183
+ " bob@host.testcontainers.internal\n";
159184
Files.write(entryPoint.toPath(), lines.getBytes(StandardCharsets.US_ASCII));
160185
// Create the container

sshd-mina/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
<exclude>**/ClientOpenSSHCertificatesTest.java</exclude>
136136
<exclude>**/SessionReKeyHostKeyExchangeTest.java</exclude>
137137
<exclude>**/HostBoundPubKeyAuthTest.java</exclude>
138-
<exclude>**/Sshd1055Test.java</exclude>
138+
<exclude>**/PortForwardingWithOpenSshTest.java</exclude>
139139
<!-- reading files from classpath doesn't work correctly w/ reusable test jar -->
140140
<exclude>**/OpenSSHCertificateTest.java</exclude>
141141
</excludes>

sshd-netty/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@
161161
<exclude>**/ClientOpenSSHCertificatesTest.java</exclude>
162162
<exclude>**/SessionReKeyHostKeyExchangeTest.java</exclude>
163163
<exclude>**/HostBoundPubKeyAuthTest.java</exclude>
164-
<exclude>**/Sshd1055Test.java</exclude>
164+
<exclude>**/PortForwardingWithOpenSshTest.java</exclude>
165165
<!-- reading files from classpath doesn't work correctly w/ reusable test jar -->
166166
<exclude>**/OpenSSHCertificateTest.java</exclude>
167167
</excludes>

0 commit comments

Comments
 (0)