Skip to content

Commit

Permalink
Issue eclipse-ee4j#2016 Better compatibility detection
Browse files Browse the repository at this point in the history
- can run with any compatible implementation, not only NPN Bootstrap
- with new JDK versions after 8u250 uses it's JSSE impl by default
- still can use older NPN bootstrap versions if configured
- can use also other implementations (openjsse)

Signed-off-by: David Matejcek <dmatej@seznam.cz>
  • Loading branch information
dmatej committed Nov 19, 2020
1 parent c030038 commit 8a616be
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -43,36 +42,18 @@

/**
* Grizzly TLS Next Protocol Negotiation support class.
*
*/
public class AlpnSupport {
private final static Logger LOGGER = Grizzly.logger(AlpnSupport.class);

private final static Map<SSLEngine, Connection<?>> SSL_TO_CONNECTION_MAP = new WeakHashMap<>();
private static final Logger LOGGER = Grizzly.logger(AlpnSupport.class);

private static final Map<SSLEngine, Connection<?>> SSL_TO_CONNECTION_MAP = new WeakHashMap<>();
private static final AlpnSupport INSTANCE;
private static final Method nativeHandshakeMethod;
private static final AplnExtensionCompatibility COMPATIBILITY;

static {
boolean isExtensionFound = false;
Method setHandshakeAlpnSelector = null;

try {
setHandshakeAlpnSelector = SSLEngine.class.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class);
} catch (Exception e) {
try {
ClassLoader.getSystemClassLoader().loadClass("sun.security.ssl.GrizzlyNPN");
isExtensionFound = true;
} catch (Exception e2) {
LOGGER.log(Level.FINE, "Native ALPN is not found:", e);
LOGGER.log(Level.FINE, "TLS ALPN extension is not found:", e2);
}
}

nativeHandshakeMethod = setHandshakeAlpnSelector;
INSTANCE = isExtensionFound
|| nativeHandshakeMethod != null
? new AlpnSupport() : null;
COMPATIBILITY = AplnExtensionCompatibility.getInstance();
LOGGER.config(() -> "Detected ALPN compatibility info: " + COMPATIBILITY);
INSTANCE = COMPATIBILITY.isAlpnExtensionAvailable() ? new AlpnSupport() : null;
}

public static boolean isEnabled() {
Expand All @@ -83,7 +64,6 @@ public static AlpnSupport getInstance() {
if (!isEnabled()) {
throw new IllegalStateException("TLS ALPN is disabled");
}

return INSTANCE;
}

Expand All @@ -110,17 +90,24 @@ private static void setConnection(final SSLEngine engine, final Connection<?> co
@Override
public void onInit(final Connection<?> connection, final SSLEngine sslEngine) {
assert sslEngine != null;

AlpnServerNegotiator negotiator = getServerNegotiator(connection);

if (negotiator != null && nativeHandshakeMethod != null) {
// Code only works for JDK9+
// sslEngine.setHandshakeApplicationProtocolSelector(negotiator);
try {
nativeHandshakeMethod.invoke(sslEngine, negotiator);
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Couldn't execute sslEngine.setHandshakeApplicationProtocolSelector", ex);
}
if (sslEngine.getUseClientMode()) {
// makes sense only for the server
return;
}
if (!COMPATIBILITY.isProtocolSelectorSetterInImpl()) {
// even when the api implements it, impl doesn't
return;
}
final AlpnServerNegotiator negotiator = getServerNegotiator(connection);
if (negotiator == null) {
return;
}
// Older JDK8 versions are missing this method in API, that's why we do this.
final Method setter = COMPATIBILITY.getProtocolSelectorSetter(sslEngine);
try {
setter.invoke(sslEngine, negotiator);
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Couldn't execute " + setter, ex);
}
}

Expand All @@ -131,7 +118,6 @@ public void onStart(final Connection<?> connection) {

if (sslEngine.getUseClientMode()) {
AlpnClientNegotiator negotiator = getClientNegotiator(connection);

if (negotiator != null) {
// add a CloseListener to ensure we remove the
// negotiator associated with this SSLEngine
Expand All @@ -147,9 +133,7 @@ public void onClosed(Closeable closeable, CloseType type) throws IOException {
}
} else {
AlpnServerNegotiator negotiator = getServerNegotiator(connection);

if (negotiator != null) {

// add a CloseListener to ensure we remove the
// negotiator associated with this SSLEngine
connection.addCloseListener(new CloseListener<Closeable, CloseType>() {
Expand All @@ -175,7 +159,6 @@ public void onFailure(Connection<?> connection, Throwable t) {
}
};


private AlpnSupport() {
}

Expand All @@ -201,7 +184,6 @@ public void setClientSideNegotiator(final Connection<?> connection, final AlpnCl

private void putServerSideNegotiator(final Object object, final AlpnServerNegotiator negotiator) {
serverSideLock.writeLock().lock();

try {
serverSideNegotiators.put(object, negotiator);
} finally {
Expand All @@ -223,7 +205,6 @@ private void putClientSideNegotiator(final Object object, final AlpnClientNegoti
private AlpnClientNegotiator getClientNegotiator(Connection<?> connection) {
AlpnClientNegotiator negotiator;
clientSideLock.readLock().lock();

try {
negotiator = clientSideNegotiators.get(connection);
if (negotiator == null) {
Expand All @@ -239,7 +220,6 @@ private AlpnClientNegotiator getClientNegotiator(Connection<?> connection) {
private AlpnServerNegotiator getServerNegotiator(Connection<?> connection) {
AlpnServerNegotiator negotiator;
serverSideLock.readLock().lock();

try {
negotiator = serverSideNegotiators.get(connection);
if (negotiator == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.grizzly.http2;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.logging.Logger;

import javax.net.ssl.SSLEngine;

class AplnExtensionCompatibility {
private static final Logger LOG = Logger.getLogger(AplnExtensionCompatibility.class.getName());

private static final String IMPL_CLASS_NAME = "sun.security.ssl.SSLEngineImpl";
private static final String METHOD_NAME = "setHandshakeApplicationProtocolSelector";

private static AplnExtensionCompatibility INSTANCE;

private final boolean alpnExtensionGrizzly;
private final boolean protocolSelectorSetterInApi;
private final boolean protocolSelectorSetterInImpl;

public static synchronized AplnExtensionCompatibility getInstance() {
if (INSTANCE == null) {
INSTANCE = new AplnExtensionCompatibility();
}
return INSTANCE;
}


public boolean isAlpnExtensionAvailable() {
return isAlpnExtensionGrizzly() || isProtocolSelectorSetterInApi() || isProtocolSelectorSetterInImpl();
}


public boolean isAlpnExtensionGrizzly() {
return alpnExtensionGrizzly;
}


public boolean isProtocolSelectorSetterInApi() {
return protocolSelectorSetterInApi;
}


public boolean isProtocolSelectorSetterInImpl() {
return protocolSelectorSetterInImpl;
}


public Method getProtocolSelectorSetter(final SSLEngine engine) {
Objects.requireNonNull(engine, "engine");
try {
// newer JSSE versions implement this method.
// some JDK8 versions (Zulu 8u265) don't see the method as public on impl
final Class<? extends SSLEngine> engineClass;
if (isHandshakeSetterInApi()) {
engineClass = SSLEngine.class;
} else {
engineClass = engine.getClass();
}
return engineClass.getMethod(METHOD_NAME, BiFunction.class);
} catch (final NoSuchMethodException e) {
throw new IllegalArgumentException("The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + engine.getClass().getName() + ".", e);
}
}


private AplnExtensionCompatibility() {
this.alpnExtensionGrizzly = isClassAvailableOnBootstrapClasspath("sun.security.ssl.GrizzlyNPN");
this.protocolSelectorSetterInApi = isHandshakeSetterInApi();
this.protocolSelectorSetterInImpl = isHandshakeSetterInImpl();
}


private static boolean isClassAvailableOnBootstrapClasspath(final String className) {
try {
ClassLoader.getSystemClassLoader().loadClass(className);
return true;
} catch (final ClassNotFoundException e) {
LOG.config("The class with the name '" + className + "' is not available on the bootstrap classpath.");
return false;
}
}


private static boolean isHandshakeSetterInImpl() {
try {
Class.forName(IMPL_CLASS_NAME).getMethod(METHOD_NAME, BiFunction.class);
return true;
} catch (final IllegalAccessError e) {
LOG.config(() -> "The class " + IMPL_CLASS_NAME + " is not accessible.");
return false;
} catch (final ClassNotFoundException | NoClassDefFoundError e) {
LOG.config(() -> "The class " + IMPL_CLASS_NAME + " cloud not be found.");
return false;
} catch (final NoSuchMethodException e) {
LOG.config(() -> "The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + IMPL_CLASS_NAME + " class.");
return false;
}
}


private static boolean isHandshakeSetterInApi() {
try {
// new grizzly bootstrap versions implement this method.
SSLEngine.class.getMethod(METHOD_NAME, BiFunction.class);
return true;
} catch (final NoSuchMethodException e) {
LOG.config("The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + SSLEngine.class.getName() + ".");
return false;
}
}


@Override
public String toString() {
return super.toString() + "ALPN available: " + isAlpnExtensionAvailable()
+ ", ALPN is Grizzly: " + isAlpnExtensionGrizzly()
+ ", setHandshakeApplicationProtocolSelector in API: " + isProtocolSelectorSetterInApi()
+ ", setHandshakeApplicationProtocolSelector in impl: " + isProtocolSelectorSetterInImpl();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public void setup(NetworkListener networkListener, FilterChainBuilder builder) {
final TCPNIOTransport transport = networkListener.getTransport();

if (networkListener.isSecure() && !AlpnSupport.isEnabled()) {
LOGGER.warning("TLS ALPN (Application-Layer Protocol Negotiation) support is not available. HTTP/2 support will not be enabled.");
LOGGER.warning("TLS ALPN (Application-Layer Protocol Negotiation) support is not available."
+ " HTTP/2 support will not be enabled.");
return;
}

Expand Down
1 change: 1 addition & 0 deletions modules/http2/src/test/resources/logging.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ java.util.logging.ConsoleHandler.formatter=org.glassfish.grizzly.utils.LoggingFo
org.glassfish.grizzly.filterchain.DefaultFilterChain.level=INFO
org.glassfish.grizzly.http.server.Request.level=INFO
org.glassfish.grizzly.http2.level=INFO
org.glassfish.grizzly.http2.AlpnSupport.level=CONFIG
org.glassfish.grizzly.http2.DefaultInputBuffer.level=INFO
org.glassfish.grizzly.http2.Http2Stream.level=INFO
org.glassfish.grizzly.http2.NetLogger.level=INFO

0 comments on commit 8a616be

Please sign in to comment.