From 247ffb137733a2fd65b5f647ebda4f5dc09492c0 Mon Sep 17 00:00:00 2001 From: nmittler Date: Fri, 24 Jul 2015 11:19:34 -0700 Subject: [PATCH] Adding support for NPN fallback. This takes some steps towards #525, but it won't be fixed until we officially support OpenSSL. This is due to the fact that ALPN->NPN fallback isn't supported with Jetty (since only one of the bootstrap plugins can be provided). --- .../io/grpc/benchmarks/qps/AsyncClient.java | 3 +- .../io/grpc/benchmarks/qps/AsyncServer.java | 16 +++- .../benchmarks/qps/ClientConfiguration.java | 8 ++ .../grpc/benchmarks/qps/OpenLoopClient.java | 3 +- .../benchmarks/qps/ServerConfiguration.java | 8 ++ .../java/io/grpc/benchmarks/qps/Utils.java | 20 +++-- build.gradle | 2 +- .../java/io/grpc/netty/GrpcSslContexts.java | 82 +++++++++++++++++-- ...ttyAlpnVerifier.java => JettyTlsUtil.java} | 28 ++++--- .../io/grpc/netty/ProtocolNegotiators.java | 33 +++----- 10 files changed, 148 insertions(+), 55 deletions(-) rename netty/src/main/java/io/grpc/netty/{JettyAlpnVerifier.java => JettyTlsUtil.java} (71%) diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java index ad1a78949c..d002e008c1 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java @@ -44,6 +44,7 @@ import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.STREAMING_R import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TESTCA; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT; +import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.USE_DEFAULT_CIPHERS; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.WARMUP_DURATION; import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_MAX_VALUE; import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_PRECISION; @@ -325,7 +326,7 @@ public class AsyncClient { public static void main(String... args) throws Exception { ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder( ADDRESS, CHANNELS, OUTSTANDING_RPCS, CLIENT_PAYLOAD, SERVER_PAYLOAD, - TLS, TESTCA, TRANSPORT, DURATION, WARMUP_DURATION, DIRECTEXECUTOR, + TLS, TESTCA, USE_DEFAULT_CIPHERS, TRANSPORT, DURATION, WARMUP_DURATION, DIRECTEXECUTOR, SAVE_HISTOGRAM, STREAMING_RPCS, FLOW_CONTROL_WINDOW); ClientConfiguration config; try { diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java index 78e292b9a7..d56bc5795c 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java @@ -50,6 +50,7 @@ import io.netty.channel.ServerChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import java.io.File; @@ -107,10 +108,17 @@ public class AsyncServer { File cert = TestUtils.loadCert("server1.pem"); File key = TestUtils.loadCert("server1.key"); - boolean useJdkSsl = config.transport == ServerConfiguration.Transport.NETTY_NIO; - sslContext = GrpcSslContexts.forServer(cert, key) - .sslProvider(useJdkSsl ? SslProvider.JDK : SslProvider.OPENSSL) - .build(); + SslContextBuilder sslContextBuilder = GrpcSslContexts.forServer(cert, key); + if (config.transport == ServerConfiguration.Transport.NETTY_NIO) { + sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK); + } else { + // Native transport with OpenSSL + sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL); + } + if (config.useDefaultCiphers) { + sslContextBuilder.ciphers(null); + } + sslContext = sslContextBuilder.build(); } final EventLoopGroup boss; diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java index fbb1aebe7c..f3c115a45c 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java @@ -60,6 +60,7 @@ class ClientConfiguration implements Configuration { Transport transport = Transport.NETTY_NIO; boolean tls; boolean testca; + boolean useDefaultCiphers; boolean directExecutor; SocketAddress address; int channels = 4; @@ -233,6 +234,13 @@ class ClientConfiguration implements Configuration { config.testca = parseBoolean(value); } }, + USE_DEFAULT_CIPHERS("", "Use the default JDK ciphers for TLS (Used to support Java 7).", + "" + DEFAULT.useDefaultCiphers) { + @Override + protected void setClientValue(ClientConfiguration config, String value) { + config.useDefaultCiphers = parseBoolean(value); + } + }, TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) { @Override protected void setClientValue(ClientConfiguration config, String value) { diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java index d444c1c682..8b407a6911 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java @@ -42,6 +42,7 @@ import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TARGET_QPS; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TESTCA; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT; +import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.USE_DEFAULT_CIPHERS; import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_MAX_VALUE; import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_PRECISION; import static io.grpc.benchmarks.qps.Utils.newClientChannel; @@ -85,7 +86,7 @@ public class OpenLoopClient { public static void main(String... args) throws Exception { ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder( ADDRESS, TARGET_QPS, CLIENT_PAYLOAD, SERVER_PAYLOAD, TLS, - TESTCA, TRANSPORT, DURATION, SAVE_HISTOGRAM, FLOW_CONTROL_WINDOW); + TESTCA, USE_DEFAULT_CIPHERS, TRANSPORT, DURATION, SAVE_HISTOGRAM, FLOW_CONTROL_WINDOW); ClientConfiguration config; try { config = configBuilder.build(args); diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java index f8bc70490b..c1553f70c8 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java @@ -55,6 +55,7 @@ class ServerConfiguration implements Configuration { Transport transport = Transport.NETTY_NIO; boolean tls; + boolean useDefaultCiphers; boolean directExecutor; SocketAddress address; int flowControlWindow = NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW; @@ -172,6 +173,13 @@ class ServerConfiguration implements Configuration { config.tls = parseBoolean(value); } }, + USE_DEFAULT_CIPHERS("", "Use the default JDK ciphers for TLS (Used to support Java 7).", + "false") { + @Override + protected void setServerValue(ServerConfiguration config, String value) { + config.useDefaultCiphers = parseBoolean(value); + } + }, TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) { @Override protected void setServerValue(ServerConfiguration config, String value) { diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/Utils.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/Utils.java index 6448979fba..8daeae5f56 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/Utils.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/Utils.java @@ -50,6 +50,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.unix.DomainSocketAddress; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import org.HdrHistogram.Histogram; @@ -153,14 +154,21 @@ final class Utils { } // It's a Netty transport. - SslContext context = null; + SslContext sslContext = null; NegotiationType negotiationType = config.tls ? NegotiationType.TLS : NegotiationType.PLAINTEXT; if (config.tls && config.testca) { File cert = TestUtils.loadCert("ca.pem"); - boolean useJdkSsl = config.transport == ClientConfiguration.Transport.NETTY_NIO; - context = GrpcSslContexts.forClient().trustManager(cert) - .sslProvider(useJdkSsl ? SslProvider.JDK : SslProvider.OPENSSL) - .build(); + SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient().trustManager(cert); + if (config.transport == ClientConfiguration.Transport.NETTY_NIO) { + sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK); + } else { + // Native transport with OpenSSL + sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL); + } + if (config.useDefaultCiphers) { + sslContextBuilder.ciphers(null); + } + sslContext = sslContextBuilder.build(); } final EventLoopGroup group; final Class channelType; @@ -192,7 +200,7 @@ final class Utils { .channelType(channelType) .negotiationType(negotiationType) .executor(config.directExecutor ? MoreExecutors.newDirectExecutorService() : null) - .sslContext(context) + .sslContext(sslContext) .flowControlWindow(config.flowControlWindow) .build(); } diff --git a/build.gradle b/build.gradle index 06eb203b0d..9d0b13b257 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ subprojects { // on the Java version. def alpnboot_version = '8.1.2.v20141202' if (JavaVersion.current().ordinal() < JavaVersion.VERSION_1_8.ordinal()) { - alpnboot_version = '7.1.2.v20141202' + alpnboot_version = '7.1.3.v20150130' } alpnboot_package_name = 'org.mortbay.jetty.alpn:alpn-boot:' + alpnboot_version diff --git a/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java index 9edce1ba5b..597cb3740d 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java +++ b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java @@ -31,12 +31,14 @@ package io.grpc.netty; +import io.grpc.internal.ExperimentalApi; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; import java.io.File; @@ -47,11 +49,25 @@ import java.io.File; public class GrpcSslContexts { private GrpcSslContexts() {} - private static ApplicationProtocolConfig DEFAULT_APN = new ApplicationProtocolConfig( - Protocol.ALPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, - "h2"); + private static String[] HTTP2_VERSIONS = {"h2"}; + + private static ApplicationProtocolConfig ALPN = new ApplicationProtocolConfig( + Protocol.ALPN, + SelectorFailureBehavior.FATAL_ALERT, + SelectedListenerFailureBehavior.FATAL_ALERT, + HTTP2_VERSIONS); + + private static ApplicationProtocolConfig NPN = new ApplicationProtocolConfig( + Protocol.NPN, + SelectorFailureBehavior.FATAL_ALERT, + SelectedListenerFailureBehavior.FATAL_ALERT, + HTTP2_VERSIONS); + + private static ApplicationProtocolConfig NPN_AND_ALPN = new ApplicationProtocolConfig( + Protocol.NPN_AND_ALPN, + SelectorFailureBehavior.FATAL_ALERT, + SelectedListenerFailureBehavior.FATAL_ALERT, + HTTP2_VERSIONS); /** * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC. @@ -89,7 +105,59 @@ public class GrpcSslContexts { * an application requires particular settings it should override the options set here. */ public static SslContextBuilder configure(SslContextBuilder builder) { - return builder.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) - .applicationProtocolConfig(DEFAULT_APN); + return configure(builder, defaultSslProvider()); + } + + /** + * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if + * an application requires particular settings it should override the options set here. + */ + @ExperimentalApi + public static SslContextBuilder configure(SslContextBuilder builder, SslProvider provider) { + return builder.sslProvider(provider) + .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) + .applicationProtocolConfig(selectApplicationProtocolConfig(provider)); + } + + /** + * Returns OpenSSL if available, otherwise returns the JDK provider. + */ + private static SslProvider defaultSslProvider() { + return SslProvider.JDK; + // TODO(nmittler): use this once we support OpenSSL. + // return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK; + } + + /** + * Attempts to select the best {@link ApplicationProtocolConfig} for the given + * {@link SslProvider}. + */ + private static ApplicationProtocolConfig selectApplicationProtocolConfig(SslProvider provider) { + switch (provider) { + case JDK: { + if (JettyTlsUtil.isJettyAlpnConfigured()) { + return ALPN; + } + if (JettyTlsUtil.isJettyNpnConfigured()) { + return NPN; + } + throw new IllegalArgumentException("Jetty ALPN/NPN has not been properly configured."); + } + case OPENSSL: { + throw new IllegalArgumentException("OpenSSL is not currently supported."); + // TODO(nmittler): use this once we support OpenSSL. + /*if (!OpenSsl.isAvailable()) { + throw new IllegalArgumentException("OpenSSL is not installed on the system."); + } + + if (OpenSsl.isAlpnSupported()) { + return NPN_AND_ALPN; + } else { + return NPN; + }*/ + } + default: + throw new IllegalArgumentException("Unsupported provider: " + provider); + } } } diff --git a/netty/src/main/java/io/grpc/netty/JettyAlpnVerifier.java b/netty/src/main/java/io/grpc/netty/JettyTlsUtil.java similarity index 71% rename from netty/src/main/java/io/grpc/netty/JettyAlpnVerifier.java rename to netty/src/main/java/io/grpc/netty/JettyTlsUtil.java index 2f2c8fdffd..f40c4ee2d5 100644 --- a/netty/src/main/java/io/grpc/netty/JettyAlpnVerifier.java +++ b/netty/src/main/java/io/grpc/netty/JettyTlsUtil.java @@ -32,31 +32,33 @@ package io.grpc.netty; /** - * Utility class that verifies that Jetty ALPN is properly configured for the system. + * Utility class for determining support for Jetty TLS ALPN/NPN. */ -final class JettyAlpnVerifier { - private JettyAlpnVerifier() { +final class JettyTlsUtil { + private JettyTlsUtil() { } /** - * Exception thrown when Jetty ALPN was not found in the boot classloader. + * Indicates whether or not the Jetty ALPN jar is installed in the boot classloader. */ - static final class NotFoundException extends Exception { - public NotFoundException(Throwable cause) { - super("Jetty ALPN not found in boot classloader.", cause); + static boolean isJettyAlpnConfigured() { + try { + Class.forName("org.eclipse.jetty.alpn.ALPN", true, null); + return true; + } catch (ClassNotFoundException e) { + return false; } } /** - * Verifies that Jetty ALPN is configured properly on this system. - * @throws NotFoundException thrown if Jetty ALPN is missing from the boot classloader. + * Indicates whether or not the Jetty NPN jar is installed in the boot classloader. */ - static void verifyJettyAlpn() throws NotFoundException { + static boolean isJettyNpnConfigured() { try { - // Check the boot classloader for the ALPN class. - Class.forName("org.eclipse.jetty.alpn.ALPN", true, null); + Class.forName("org.eclipse.jetty.npn.NextProtoNego", true, null); + return true; } catch (ClassNotFoundException e) { - throw new NotFoundException(e); + return false; } } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index c2da30860f..79fbf07e57 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -48,7 +48,6 @@ 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.OpenSslContext; -import io.netty.handler.ssl.OpenSslEngine; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; @@ -74,15 +73,6 @@ public final class ProtocolNegotiators { public static ChannelHandler serverTls(SSLEngine sslEngine) { Preconditions.checkNotNull(sslEngine, "sslEngine"); - // If we're using Jetty ALPN, verify that it is configured properly. - if (!(sslEngine instanceof OpenSslEngine)) { - try { - JettyAlpnVerifier.verifyJettyAlpn(); - } catch (JettyAlpnVerifier.NotFoundException e) { - throw new IllegalArgumentException(e); - } - } - return new SslHandler(sslEngine, false); } @@ -96,24 +86,23 @@ public final class ProtocolNegotiators { Preconditions.checkNotNull(sslContext, "sslContext"); Preconditions.checkNotNull(inetAddress, "inetAddress"); - // If we're using Jetty ALPN, verify that it is configured properly. - if (!(sslContext instanceof OpenSslContext)) { - try { - JettyAlpnVerifier.verifyJettyAlpn(); - } catch (JettyAlpnVerifier.NotFoundException e) { - throw new IllegalArgumentException(e); - } - } - return new ProtocolNegotiator() { @Override public Handler newHandler(Http2ConnectionHandler handler) { ChannelHandler sslBootstrap = new ChannelHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - // TODO(nmittler): Unsupported for OpenSSL in Netty < 4.1.Beta6. - SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), - inetAddress.getHostName(), inetAddress.getPort()); + final SSLEngine sslEngine; + if (sslContext instanceof OpenSslContext) { + // TODO(nmittler): Unsupported for OpenSSL in Netty < 4.1.Beta6. + // Until we upgrade Netty, uncomment the line below when testing with OpenSSL. + //sslEngine = sslContext.newEngine(ctx.alloc()); + sslEngine = sslContext.newEngine(ctx.alloc(), + inetAddress.getHostName(), inetAddress.getPort()); + } else { + sslEngine = sslContext.newEngine(ctx.alloc(), + inetAddress.getHostName(), inetAddress.getPort()); + } SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslEngine.setSSLParameters(sslParams);