Skip to content

Commit

Permalink
SslHandler promise completion incorrect if write doesn't immediately
Browse files Browse the repository at this point in the history
complete

Motivation:
SslHandler removes a Buffer/Promise pair from
AbstractCoalescingBufferQueue when wrapping data. However it is possible
the SSLEngine will not consume the entire buffer. In this case
SslHandler adds the Buffer back to the queue, but doesn't add the
Promise back to the queue. This may result in the promise completing
immediately in finishFlush, and generally not correlating to the
completion of writing the corresponding Buffer

Modifications:
- AbstractCoalescingBufferQueue#addFirst should also support adding the
ChannelPromise
- In the event of a handshake timeout we should immediately fail pending
writes immediately to get a more accurate exception

Result:
Fixes netty#7378.
  • Loading branch information
Scottmitch committed Nov 7, 2017
1 parent 8618a33 commit ae58af8
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 6 deletions.
21 changes: 16 additions & 5 deletions handler/src/main/java/io/netty/handler/ssl/SslHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,10 @@ private void wrap(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLExcepti
return;
} else {
if (buf.isReadable()) {
pendingUnencryptedWrites.addFirst(buf);
pendingUnencryptedWrites.addFirst(buf, promise);
// When we add the buffer/promise pair back we need to be sure we don't complete the promise
// later in finishWrap. We only complete the promise if the buffer is completely consumed.
promise = null;
} else {
buf.release();
}
Expand Down Expand Up @@ -1515,9 +1518,13 @@ private void setHandshakeFailure(ChannelHandlerContext ctx, Throwable cause, boo
notifyHandshakeFailure(cause);
} finally {
// Ensure we remove and fail all pending writes in all cases and so release memory quickly.
if (pendingUnencryptedWrites != null) {
pendingUnencryptedWrites.releaseAndFailAll(ctx, cause);
}
releaseAndFailAll(cause);
}
}

private void releaseAndFailAll(Throwable cause) {
if (pendingUnencryptedWrites != null) {
pendingUnencryptedWrites.releaseAndFailAll(ctx, cause);
}
}

Expand Down Expand Up @@ -1701,7 +1708,11 @@ public void run() {
if (promise.isDone()) {
return;
}
notifyHandshakeFailure(HANDSHAKE_TIMED_OUT);
try {
notifyHandshakeFailure(HANDSHAKE_TIMED_OUT);
} finally {
releaseAndFailAll(HANDSHAKE_TIMED_OUT);
}
}
}, handshakeTimeoutMillis, TimeUnit.MILLISECONDS);

Expand Down
18 changes: 18 additions & 0 deletions handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.embedded.EmbeddedChannel;
Expand Down Expand Up @@ -57,6 +58,7 @@
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.BlockingQueue;
Expand Down Expand Up @@ -185,6 +187,22 @@ public void testNonByteBufNotPassThrough() throws Exception {
}
}

@Test
public void testIncompleteWriteDoesNotCompletePromisePrematurely() throws NoSuchAlgorithmException {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
engine.setUseClientMode(false);

EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine));

ChannelPromise promise = ch.newPromise();
ByteBuf buf = Unpooled.buffer(10).writeZero(10);
ch.writeAndFlush(buf, promise);
assertFalse(promise.isDone());
assertTrue(ch.finishAndReleaseAll());
assertTrue(promise.isDone());
assertThat(promise.cause(), is(instanceOf(SSLException.class)));
}

@Test
public void testReleaseSslEngine() throws Exception {
assumeTrue(OpenSsl.isAvailable());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ protected AbstractCoalescingBufferQueue(Channel channel, int initSize) {
/**
* Add a buffer to the front of the queue.
*/
public final void addFirst(ByteBuf buf) {
public final void addFirst(ByteBuf buf, ChannelPromise promise) {
// Listener would be added here, but since it is null there is no need. The assumption is there is already a
// listener at the front of the queue, or there is a buffer at the front of the queue, which was spliced from
// buf via remove().
bufAndListenerPairs.addFirst(new DelegatingChannelPromiseNotifier(promise));
bufAndListenerPairs.addFirst(buf);

incrementReadableBytes(buf.readableBytes());
Expand Down

0 comments on commit ae58af8

Please sign in to comment.