diff --git a/build.gradle b/build.gradle index c1ab46cf264..1ecfc0f9163 100644 --- a/build.gradle +++ b/build.gradle @@ -166,14 +166,18 @@ configure(subprojects - project(":grpc-android")) { // Define a separate configuration for managing the dependency on Jetty alpnboot jar. configurations { alpnboot + tcnative } dependencies { testCompile libraries.junit, libraries.mockito - // Make the Jetty alpnboot jar available to submodules via the alpnboot configuration. + // Configuration for modules that use Jetty ALPN alpnboot alpnboot_package_name + + // Configuration for modules that use Netty tcnative (for OpenSSL). + tcnative libraries.netty_tcnative } signing { diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index 6c60c38b553..a6b197b1d59 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -27,31 +27,32 @@ dependencies { } test { + // For the automated tests, use Jetty ALPN. jvmArgs "-Xbootclasspath/p:" + configurations.alpnboot.asPath } +// For the generated scripts, use Netty tcnative (i.e. OpenSSL). +// Note that OkHttp currently only supports ALPN, so OpenSSL version >= 1.0.2 is required. + task test_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.TestServiceClient" applicationName = "test-client" - defaultJvmOpts = ["-Xbootclasspath/p:" + configurations.alpnboot.asPath] outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = jar.outputs.files + configurations.runtime + configurations.tcnative } task test_server(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.TestServiceServer" applicationName = "test-server" - defaultJvmOpts = ["-Xbootclasspath/p:" + configurations.alpnboot.asPath] outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = jar.outputs.files + configurations.runtime + configurations.tcnative } task reconnect_test_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.ReconnectTestClient" applicationName = "reconnect-test-client" - defaultJvmOpts = ["-Xbootclasspath/p:" + configurations.alpnboot.asPath] outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = jar.outputs.files + configurations.runtime + configurations.tcnative } applicationDistribution.into("bin") { diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 4aadae8b136..20e54f44f1b 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -47,6 +47,8 @@ import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.codec.http2.Http2ConnectionHandler; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslEngine; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; @@ -54,6 +56,7 @@ import java.net.InetSocketAddress; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; @@ -84,18 +87,23 @@ public void handlerAdded(ChannelHandlerContext ctx) throws Exception { ctx.pipeline().addFirst(sslHandler); } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + fail(ctx, cause); + } + @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt; if (handshakeEvent.isSuccess()) { - SslHandler handler = ctx.pipeline().get(SslHandler.class); - if (handler.applicationProtocol() != null) { + if (applicationProtocol(ctx) != null) { // Successfully negotiated the protocol. ctx.pipeline().remove(this); } else { + fail(ctx, new Exception( - "Failed ALPN negotiation: Unable to find compatible protocol.")); + "Failed protocol negotiation: Unable to find compatible protocol.")); } } else { fail(ctx, handshakeEvent.cause()); @@ -105,9 +113,39 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } private void fail(ChannelHandlerContext ctx, Throwable exception) { - log.log(Level.FINEST, "TLS negotiation failed for new client.", exception); + log.log(Level.FINE, errorMessage(ctx), exception); ctx.close(); } + + private String errorMessage(ChannelHandlerContext ctx) { + StringBuilder builder = new StringBuilder("TLS negotiation failed for new client. "); + SSLEngine engine = sslHandler(ctx).engine(); + if (engine instanceof OpenSslEngine) { + builder.append("OpenSSL version: "); + builder.append("0x").append(Integer.toHexString(OpenSsl.version())); + builder.append(" [").append(OpenSsl.versionString()).append(']'); + builder.append(". ALPN supported: ").append(OpenSsl.isAlpnSupported()).append(". "); + } else if (JettyTlsUtil.isJettyAlpnConfigured()) { + builder.append("Jetty ALPN configured. "); + } else if (JettyTlsUtil.isJettyNpnConfigured()) { + builder.append("Jetty NPN configured. "); + } + builder.append("Enabled ciphers="); + builder.append(Arrays.toString(enabledCiphers(ctx))).append(". "); + return builder.toString(); + } + + private String applicationProtocol(ChannelHandlerContext ctx) { + return sslHandler(ctx).applicationProtocol(); + } + + private String[] enabledCiphers(ChannelHandlerContext ctx) { + return sslHandler(ctx).engine().getEnabledCipherSuites(); + } + + private SslHandler sslHandler(ChannelHandlerContext ctx) { + return ctx.pipeline().get(SslHandler.class); + } }; }