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).
This commit is contained in:
nmittler 2015-07-24 11:19:34 -07:00
parent 1b1c646ccd
commit 247ffb1377
10 changed files with 148 additions and 55 deletions

View File

@ -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.TESTCA;
import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS; 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.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.ClientConfiguration.ClientParam.WARMUP_DURATION;
import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_MAX_VALUE; 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.HISTOGRAM_PRECISION;
@ -325,7 +326,7 @@ public class AsyncClient {
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder( ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder(
ADDRESS, CHANNELS, OUTSTANDING_RPCS, CLIENT_PAYLOAD, SERVER_PAYLOAD, 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); SAVE_HISTOGRAM, STREAMING_RPCS, FLOW_CONTROL_WINDOW);
ClientConfiguration config; ClientConfiguration config;
try { try {

View File

@ -50,6 +50,7 @@ import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import java.io.File; import java.io.File;
@ -107,10 +108,17 @@ public class AsyncServer {
File cert = TestUtils.loadCert("server1.pem"); File cert = TestUtils.loadCert("server1.pem");
File key = TestUtils.loadCert("server1.key"); File key = TestUtils.loadCert("server1.key");
boolean useJdkSsl = config.transport == ServerConfiguration.Transport.NETTY_NIO; SslContextBuilder sslContextBuilder = GrpcSslContexts.forServer(cert, key);
sslContext = GrpcSslContexts.forServer(cert, key) if (config.transport == ServerConfiguration.Transport.NETTY_NIO) {
.sslProvider(useJdkSsl ? SslProvider.JDK : SslProvider.OPENSSL) sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK);
.build(); } else {
// Native transport with OpenSSL
sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL);
}
if (config.useDefaultCiphers) {
sslContextBuilder.ciphers(null);
}
sslContext = sslContextBuilder.build();
} }
final EventLoopGroup boss; final EventLoopGroup boss;

View File

@ -60,6 +60,7 @@ class ClientConfiguration implements Configuration {
Transport transport = Transport.NETTY_NIO; Transport transport = Transport.NETTY_NIO;
boolean tls; boolean tls;
boolean testca; boolean testca;
boolean useDefaultCiphers;
boolean directExecutor; boolean directExecutor;
SocketAddress address; SocketAddress address;
int channels = 4; int channels = 4;
@ -233,6 +234,13 @@ class ClientConfiguration implements Configuration {
config.testca = parseBoolean(value); 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()) { TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) {
@Override @Override
protected void setClientValue(ClientConfiguration config, String value) { protected void setClientValue(ClientConfiguration config, String value) {

View File

@ -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.TESTCA;
import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS; 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.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_MAX_VALUE;
import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_PRECISION; import static io.grpc.benchmarks.qps.Utils.HISTOGRAM_PRECISION;
import static io.grpc.benchmarks.qps.Utils.newClientChannel; import static io.grpc.benchmarks.qps.Utils.newClientChannel;
@ -85,7 +86,7 @@ public class OpenLoopClient {
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder( ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder(
ADDRESS, TARGET_QPS, CLIENT_PAYLOAD, SERVER_PAYLOAD, TLS, 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; ClientConfiguration config;
try { try {
config = configBuilder.build(args); config = configBuilder.build(args);

View File

@ -55,6 +55,7 @@ class ServerConfiguration implements Configuration {
Transport transport = Transport.NETTY_NIO; Transport transport = Transport.NETTY_NIO;
boolean tls; boolean tls;
boolean useDefaultCiphers;
boolean directExecutor; boolean directExecutor;
SocketAddress address; SocketAddress address;
int flowControlWindow = NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW; int flowControlWindow = NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW;
@ -172,6 +173,13 @@ class ServerConfiguration implements Configuration {
config.tls = parseBoolean(value); 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()) { TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) {
@Override @Override
protected void setServerValue(ServerConfiguration config, String value) { protected void setServerValue(ServerConfiguration config, String value) {

View File

@ -50,6 +50,7 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.DomainSocketAddress; import io.netty.channel.unix.DomainSocketAddress;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import org.HdrHistogram.Histogram; import org.HdrHistogram.Histogram;
@ -153,14 +154,21 @@ final class Utils {
} }
// It's a Netty transport. // It's a Netty transport.
SslContext context = null; SslContext sslContext = null;
NegotiationType negotiationType = config.tls ? NegotiationType.TLS : NegotiationType.PLAINTEXT; NegotiationType negotiationType = config.tls ? NegotiationType.TLS : NegotiationType.PLAINTEXT;
if (config.tls && config.testca) { if (config.tls && config.testca) {
File cert = TestUtils.loadCert("ca.pem"); File cert = TestUtils.loadCert("ca.pem");
boolean useJdkSsl = config.transport == ClientConfiguration.Transport.NETTY_NIO; SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient().trustManager(cert);
context = GrpcSslContexts.forClient().trustManager(cert) if (config.transport == ClientConfiguration.Transport.NETTY_NIO) {
.sslProvider(useJdkSsl ? SslProvider.JDK : SslProvider.OPENSSL) sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK);
.build(); } else {
// Native transport with OpenSSL
sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL);
}
if (config.useDefaultCiphers) {
sslContextBuilder.ciphers(null);
}
sslContext = sslContextBuilder.build();
} }
final EventLoopGroup group; final EventLoopGroup group;
final Class<? extends io.netty.channel.Channel> channelType; final Class<? extends io.netty.channel.Channel> channelType;
@ -192,7 +200,7 @@ final class Utils {
.channelType(channelType) .channelType(channelType)
.negotiationType(negotiationType) .negotiationType(negotiationType)
.executor(config.directExecutor ? MoreExecutors.newDirectExecutorService() : null) .executor(config.directExecutor ? MoreExecutors.newDirectExecutorService() : null)
.sslContext(context) .sslContext(sslContext)
.flowControlWindow(config.flowControlWindow) .flowControlWindow(config.flowControlWindow)
.build(); .build();
} }

View File

@ -145,7 +145,7 @@ subprojects {
// on the Java version. // on the Java version.
def alpnboot_version = '8.1.2.v20141202' def alpnboot_version = '8.1.2.v20141202'
if (JavaVersion.current().ordinal() < JavaVersion.VERSION_1_8.ordinal()) { 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 alpnboot_package_name = 'org.mortbay.jetty.alpn:alpn-boot:' + alpnboot_version

View File

@ -31,12 +31,14 @@
package io.grpc.netty; package io.grpc.netty;
import io.grpc.internal.ExperimentalApi;
import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter; import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import java.io.File; import java.io.File;
@ -47,11 +49,25 @@ import java.io.File;
public class GrpcSslContexts { public class GrpcSslContexts {
private GrpcSslContexts() {} private GrpcSslContexts() {}
private static ApplicationProtocolConfig DEFAULT_APN = new ApplicationProtocolConfig( private static String[] HTTP2_VERSIONS = {"h2"};
Protocol.ALPN,
SelectorFailureBehavior.FATAL_ALERT, private static ApplicationProtocolConfig ALPN = new ApplicationProtocolConfig(
SelectedListenerFailureBehavior.FATAL_ALERT, Protocol.ALPN,
"h2"); 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. * 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. * an application requires particular settings it should override the options set here.
*/ */
public static SslContextBuilder configure(SslContextBuilder builder) { public static SslContextBuilder configure(SslContextBuilder builder) {
return builder.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) return configure(builder, defaultSslProvider());
.applicationProtocolConfig(DEFAULT_APN); }
/**
* 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);
}
} }
} }

View File

@ -32,31 +32,33 @@
package io.grpc.netty; 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 { final class JettyTlsUtil {
private JettyAlpnVerifier() { 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 { static boolean isJettyAlpnConfigured() {
public NotFoundException(Throwable cause) { try {
super("Jetty ALPN not found in boot classloader.", cause); 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. * Indicates whether or not the Jetty NPN jar is installed in the boot classloader.
* @throws NotFoundException thrown if Jetty ALPN is missing from the boot classloader.
*/ */
static void verifyJettyAlpn() throws NotFoundException { static boolean isJettyNpnConfigured() {
try { try {
// Check the boot classloader for the ALPN class. Class.forName("org.eclipse.jetty.npn.NextProtoNego", true, null);
Class.forName("org.eclipse.jetty.alpn.ALPN", true, null); return true;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new NotFoundException(e); return false;
} }
} }
} }

View File

@ -48,7 +48,6 @@ import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
import io.netty.handler.codec.http2.Http2ConnectionHandler; import io.netty.handler.codec.http2.Http2ConnectionHandler;
import io.netty.handler.ssl.OpenSslContext; import io.netty.handler.ssl.OpenSslContext;
import io.netty.handler.ssl.OpenSslEngine;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.handler.ssl.SslHandshakeCompletionEvent;
@ -74,15 +73,6 @@ public final class ProtocolNegotiators {
public static ChannelHandler serverTls(SSLEngine sslEngine) { public static ChannelHandler serverTls(SSLEngine sslEngine) {
Preconditions.checkNotNull(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); return new SslHandler(sslEngine, false);
} }
@ -96,24 +86,23 @@ public final class ProtocolNegotiators {
Preconditions.checkNotNull(sslContext, "sslContext"); Preconditions.checkNotNull(sslContext, "sslContext");
Preconditions.checkNotNull(inetAddress, "inetAddress"); 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() { return new ProtocolNegotiator() {
@Override @Override
public Handler newHandler(Http2ConnectionHandler handler) { public Handler newHandler(Http2ConnectionHandler handler) {
ChannelHandler sslBootstrap = new ChannelHandlerAdapter() { ChannelHandler sslBootstrap = new ChannelHandlerAdapter() {
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// TODO(nmittler): Unsupported for OpenSSL in Netty < 4.1.Beta6. final SSLEngine sslEngine;
SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), if (sslContext instanceof OpenSslContext) {
inetAddress.getHostName(), inetAddress.getPort()); // 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(); SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslParams.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParams); sslEngine.setSSLParameters(sslParams);