netty: TCP close during TLS handshake should be UNAVAILABLE

Normally the first exception/event experienced is the cause and is
followed by a stampede of ClosedChannelExceptions. In this case,
SslHandler is manufacturing a ClosedChannelException of its own and
propagating it _before_ the trigger event. This might be considered a
bug, but changing SslHandler's behavior would be very risky and almost
certainly break someone's code.

Fixes #7376
This commit is contained in:
Eric Anderson 2020-10-01 09:18:26 -07:00 committed by Eric Anderson
parent 0cd56c29d6
commit ec0d01d7a4
2 changed files with 29 additions and 1 deletions

View File

@ -58,6 +58,7 @@ import io.netty.util.Attribute;
import io.netty.util.AttributeMap;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.logging.Level;
@ -372,7 +373,18 @@ final class ProtocolNegotiators {
ctx.fireExceptionCaught(ex);
}
} else {
ctx.fireExceptionCaught(handshakeEvent.cause());
Throwable t = handshakeEvent.cause();
if (t instanceof ClosedChannelException) {
// On channelInactive(), SslHandler creates its own ClosedChannelException and
// propagates it before the actual channelInactive(). So we assume here that any
// such exception is from channelInactive() and emulate the normal behavior of
// WriteBufferingAndExceptionHandler
t = Status.UNAVAILABLE
.withDescription("Connection closed while performing TLS negotiation")
.withCause(t)
.asRuntimeException();
}
ctx.fireExceptionCaught(t);
}
} else {
super.userEventTriggered0(ctx, evt);

View File

@ -33,6 +33,8 @@ import io.grpc.Attributes;
import io.grpc.Grpc;
import io.grpc.InternalChannelz.Security;
import io.grpc.SecurityLevel;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.internal.GrpcAttributes;
import io.grpc.internal.testing.TestUtils;
import io.grpc.netty.ProtocolNegotiators.ClientTlsHandler;
@ -534,6 +536,20 @@ public class ProtocolNegotiatorsTest {
assertNull(grpcHandlerCtx);
}
@Test
public void clientTlsHandler_closeDuringNegotiation() throws Exception {
ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", null);
pipeline.addLast(new WriteBufferingAndExceptionHandler(handler));
ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE);
// SslHandler fires userEventTriggered() before channelInactive()
pipeline.fireChannelInactive();
assertThat(pendingWrite.cause()).isInstanceOf(StatusRuntimeException.class);
assertThat(Status.fromThrowable(pendingWrite.cause()).getCode())
.isEqualTo(Status.Code.UNAVAILABLE);
}
@Test
public void engineLog() {
ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null);