Skip to content

Commit ed5a9e1

Browse files
committed
Enforce connection timeout in NIO TLS handshake
The handshake is in blocking mode and reading from a channel in this mode does not honor so_timeout but using temporary channels based on the socket input/output streams offers a decent workaround for this stage. Fixes #719 (cherry picked from commit 2751d46) Conflicts: pom.xml
1 parent fcc8cf3 commit ed5a9e1

File tree

4 files changed

+102
-22
lines changed

4 files changed

+102
-22
lines changed

pom.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
<mockito.version>4.0.0</mockito.version>
6464
<assertj.version>3.21.0</assertj.version>
6565
<jetty.version>9.4.44.v20210927</jetty.version>
66-
<bouncycastle.version>1.69</bouncycastle.version>
66+
<bouncycastle.version>1.70</bouncycastle.version>
67+
<netcrusher.version>0.10</netcrusher.version>
6768

6869
<maven.javadoc.plugin.version>3.2.0</maven.javadoc.plugin.version>
6970
<maven.release.plugin.version>2.5.3</maven.release.plugin.version>
@@ -759,6 +760,13 @@
759760
<version>${bouncycastle.version}</version>
760761
<scope>test</scope>
761762
</dependency>
763+
<dependency>
764+
<groupId>com.github.netcrusherorg</groupId>
765+
<artifactId>netcrusher-core</artifactId>
766+
<version>${netcrusher.version}</version>
767+
<scope>test</scope>
768+
</dependency>
769+
762770
</dependencies>
763771

764772
<build>

src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerFactory.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
1+
// Copyright (c) 2007-2022 VMware, Inc. or its affiliates. All rights reserved.
22
//
33
// This software, the RabbitMQ Java client library, is triple-licensed under the
44
// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2
@@ -21,6 +21,9 @@
2121
import com.rabbitmq.client.impl.AbstractFrameHandlerFactory;
2222
import com.rabbitmq.client.impl.FrameHandler;
2323
import com.rabbitmq.client.impl.TlsUtils;
24+
import java.nio.channels.Channels;
25+
import java.nio.channels.ReadableByteChannel;
26+
import java.nio.channels.WritableByteChannel;
2427
import org.slf4j.Logger;
2528
import org.slf4j.LoggerFactory;
2629

@@ -95,14 +98,23 @@ public FrameHandler create(Address addr, String connectionName) throws IOExcepti
9598

9699
channel.connect(address);
97100

101+
98102
if (ssl) {
103+
int initialSoTimeout = channel.socket().getSoTimeout();
104+
channel.socket().setSoTimeout(this.connectionTimeout);
99105
sslEngine.beginHandshake();
100106
try {
101-
boolean handshake = SslEngineHelper.doHandshake(channel, sslEngine);
107+
ReadableByteChannel wrappedReadChannel = Channels.newChannel(
108+
channel.socket().getInputStream());
109+
WritableByteChannel wrappedWriteChannel = Channels.newChannel(
110+
channel.socket().getOutputStream());
111+
boolean handshake = SslEngineHelper.doHandshake(
112+
wrappedWriteChannel, wrappedReadChannel, sslEngine);
102113
if (!handshake) {
103114
LOGGER.error("TLS connection failed");
104115
throw new SSLException("TLS handshake failed");
105116
}
117+
channel.socket().setSoTimeout(initialSoTimeout);
106118
} catch (SSLHandshakeException e) {
107119
LOGGER.error("TLS connection failed: {}", e.getMessage());
108120
throw e;

src/main/java/com/rabbitmq/client/impl/nio/SslEngineHelper.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
1+
// Copyright (c) 2007-2022 VMware, Inc. or its affiliates. All rights reserved.
22
//
33
// This software, the RabbitMQ Java client library, is triple-licensed under the
44
// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2
@@ -21,7 +21,6 @@
2121
import java.io.IOException;
2222
import java.nio.ByteBuffer;
2323
import java.nio.channels.ReadableByteChannel;
24-
import java.nio.channels.SocketChannel;
2524
import java.nio.channels.WritableByteChannel;
2625
import org.slf4j.Logger;
2726
import org.slf4j.LoggerFactory;
@@ -38,7 +37,7 @@ public class SslEngineHelper {
3837

3938
private static final Logger LOGGER = LoggerFactory.getLogger(SslEngineHelper.class);
4039

41-
public static boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
40+
public static boolean doHandshake(WritableByteChannel writeChannel, ReadableByteChannel readChannel, SSLEngine engine) throws IOException {
4241

4342
ByteBuffer plainOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
4443
ByteBuffer plainIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
@@ -58,11 +57,11 @@ public static boolean doHandshake(SocketChannel socketChannel, SSLEngine engine)
5857
break;
5958
case NEED_UNWRAP:
6059
LOGGER.debug("Unwrapping...");
61-
handshakeStatus = unwrap(cipherIn, plainIn, socketChannel, engine);
60+
handshakeStatus = unwrap(cipherIn, plainIn, readChannel, engine);
6261
break;
6362
case NEED_WRAP:
6463
LOGGER.debug("Wrapping...");
65-
handshakeStatus = wrap(plainOut, cipherOut, socketChannel, engine);
64+
handshakeStatus = wrap(plainOut, cipherOut, writeChannel, engine);
6665
break;
6766
case FINISHED:
6867
break;

src/test/java/com/rabbitmq/client/test/ssl/NioTlsUnverifiedConnection.java

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
1+
// Copyright (c) 2007-2022 VMware, Inc. or its affiliates. All rights reserved.
22
//
33
// This software, the RabbitMQ Java client library, is triple-licensed under the
44
// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2
@@ -15,31 +15,39 @@
1515

1616
package com.rabbitmq.client.test.ssl;
1717

18-
import com.rabbitmq.client.*;
18+
import static com.rabbitmq.client.test.TestUtils.basicGetBasicConsume;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.junit.Assert.assertTrue;
21+
import static org.junit.Assert.fail;
22+
23+
import com.rabbitmq.client.Connection;
24+
import com.rabbitmq.client.ConnectionFactory;
25+
import com.rabbitmq.client.TrustEverythingTrustManager;
1926
import com.rabbitmq.client.impl.nio.NioParams;
2027
import com.rabbitmq.client.test.BrokerTestCase;
2128
import com.rabbitmq.client.test.TestUtils;
29+
import java.io.IOException;
30+
import java.net.InetSocketAddress;
31+
import java.net.SocketTimeoutException;
2232
import java.util.Collection;
33+
import java.util.concurrent.CountDownLatch;
34+
import java.util.concurrent.ExecutorService;
35+
import java.util.concurrent.Executors;
36+
import java.util.concurrent.TimeUnit;
37+
import java.util.concurrent.TimeoutException;
38+
import java.util.concurrent.atomic.AtomicBoolean;
2339
import java.util.concurrent.atomic.AtomicReference;
2440
import java.util.stream.Collectors;
2541
import java.util.stream.Stream;
2642
import javax.net.ssl.SSLContext;
43+
import javax.net.ssl.SSLEngine;
2744
import javax.net.ssl.TrustManager;
2845
import org.junit.Test;
46+
import org.netcrusher.core.reactor.NioReactor;
47+
import org.netcrusher.tcp.TcpCrusher;
48+
import org.netcrusher.tcp.TcpCrusherBuilder;
2949
import org.slf4j.LoggerFactory;
3050

31-
import javax.net.ssl.SSLEngine;
32-
import java.io.IOException;
33-
import java.util.concurrent.CountDownLatch;
34-
import java.util.concurrent.TimeUnit;
35-
import java.util.concurrent.TimeoutException;
36-
import java.util.concurrent.atomic.AtomicBoolean;
37-
38-
import static com.rabbitmq.client.test.TestUtils.basicGetBasicConsume;
39-
import static org.assertj.core.api.Assertions.assertThat;
40-
import static org.junit.Assert.assertTrue;
41-
import static org.junit.Assert.fail;
42-
4351
/**
4452
*
4553
*/
@@ -149,6 +157,59 @@ public void connectionGetConsumeProtocols() throws Exception {
149157
}
150158
}
151159

160+
@Test
161+
public void connectionShouldEnforceConnectionTimeout() throws Exception {
162+
int amqpPort = 5671; // assumes RabbitMQ server running on localhost;
163+
int amqpProxyPort = TestUtils.randomNetworkPort();
164+
165+
int connectionTimeout = 3_000;
166+
int handshakeTimeout = 1_000;
167+
168+
try (NioReactor reactor = new NioReactor();
169+
TcpCrusher tcpProxy =
170+
TcpCrusherBuilder.builder()
171+
.withReactor(reactor)
172+
.withBindAddress(new InetSocketAddress(amqpProxyPort))
173+
.withConnectAddress("localhost", amqpPort)
174+
.build()) {
175+
176+
tcpProxy.open();
177+
tcpProxy.freeze();
178+
179+
ConnectionFactory factory = new ConnectionFactory();
180+
factory.setHost("localhost");
181+
factory.setPort(amqpProxyPort);
182+
183+
factory.useSslProtocol();
184+
factory.useNio();
185+
186+
factory.setConnectionTimeout(connectionTimeout);
187+
factory.setHandshakeTimeout(handshakeTimeout);
188+
189+
ExecutorService executorService = Executors.newSingleThreadExecutor();
190+
try {
191+
CountDownLatch latch = new CountDownLatch(1);
192+
executorService.submit(
193+
() -> {
194+
try {
195+
factory.newConnection();
196+
latch.countDown();
197+
} catch (SocketTimeoutException e) {
198+
latch.countDown();
199+
} catch (Exception e) {
200+
// not supposed to happen
201+
}
202+
});
203+
204+
boolean connectionCreatedTimedOut = latch.await(10, TimeUnit.SECONDS);
205+
assertThat(connectionCreatedTimedOut).isTrue();
206+
207+
} finally {
208+
executorService.shutdownNow();
209+
}
210+
}
211+
}
212+
152213
private void sendAndVerifyMessage(int size) throws Exception {
153214
CountDownLatch latch = new CountDownLatch(1);
154215
boolean messageReceived = basicGetBasicConsume(connection, QUEUE, latch, size);

0 commit comments

Comments
 (0)