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.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 {

View File

@ -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;

View File

@ -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) {

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.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);

View File

@ -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) {

View File

@ -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<? extends io.netty.channel.Channel> 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();
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

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.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);