Skip to content

Commit

Permalink
Add zero rtt support for client (java-native-access#362)
Browse files Browse the repository at this point in the history
Motivation:

Support 0-RTT feature on client side

Modifications:

- Export QUIC session using boringSSL api SSL_CTX_sess_set_new_cb and class BoringSSLSessionCallback
- Restore QUIC session using quiche api quiche_conn_set_session
- Add EarlyDataSendCallback to send 0-RTT data after send Initial packet
- Add QuicClientSessionCache to store session on client side
- Add 0-RTT example
Result:

Now it's possible to send 0-RTT packet on client side

Co-authored-by: Norman Maurer <[email protected]>
  • Loading branch information
lostk1ng and normanmaurer authored Nov 27, 2021
1 parent 7f597c2 commit 07d872f
Show file tree
Hide file tree
Showing 17 changed files with 847 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ static long SSLContext_new(boolean server, String[] applicationProtocols,
BoringSSLCertificateVerifyCallback verifyCallback,
BoringSSLTlsextServernameCallback servernameCallback,
BoringSSLKeylogCallback keylogCallback,
BoringSSLSessionCallback sessionCallback,
int verifyMode,
byte[][] subjectNames) {
return SSLContext_new0(server, toWireFormat(applicationProtocols),
handshakeCompleteCallback,
certificateCallback, verifyCallback, servernameCallback, keylogCallback, verifyMode, subjectNames);
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
keylogCallback, sessionCallback, verifyMode, subjectNames);
}

private static byte[] toWireFormat(String[] applicationProtocols) {
Expand All @@ -70,6 +71,7 @@ private static native long SSLContext_new0(boolean server,
byte[] applicationProtocols, Object handshakeCompleteCallback,
Object certificateCallback, Object verifyCallback,
Object servernameCallback, Object keylogCallback,
Object sessionCallback,
int verifyDepth, byte[][] subjectNames);
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
static native long SSLContext_setSessionCacheSize(long context, long size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class BoringSSLHandshakeCompleteCallback {
@SuppressWarnings("unused")
void handshakeComplete(long ssl, byte[] id, String cipher, String protocol, byte[] peerCertificate,
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol) {
QuicheQuicSslEngine engine = map.remove(ssl);
QuicheQuicSslEngine engine = map.get(ssl);
if (engine != null) {
engine.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime,
timeout, applicationProtocol);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.incubator.codec.quic;

import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

final class BoringSSLSessionCallback {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLSessionCallback.class);
private final QuicClientSessionCache sessionCache;
private final QuicheQuicSslEngineMap engineMap;

BoringSSLSessionCallback(QuicheQuicSslEngineMap engineMap, QuicClientSessionCache sessionCache) {
this.engineMap = engineMap;
this.sessionCache = sessionCache;
}

@SuppressWarnings("unused")
void newSession(long ssl, long creationTime, long timeout, byte[] session, boolean isSingleUse, byte[] peerParams) {
if (sessionCache == null) {
return;
}

QuicheQuicSslEngine engine = engineMap.get(ssl);
if (engine == null) {
logger.warn("engine is null ssl: {}", ssl);
return;
}

if (peerParams == null) {
peerParams = EmptyArrays.EMPTY_BYTES;
}
if (logger.isDebugEnabled()) {
logger.debug("ssl: {}, session: {}, peerParams: {}", ssl, Arrays.toString(session),
Arrays.toString(peerParams));
}
byte[] quicSession = toQuicheQuicSession(session, peerParams);
if (quicSession != null) {
logger.debug("save session host={}, port={}",
engine.getSession().getPeerHost(), engine.getSession().getPeerPort());
sessionCache.saveSession(engine.getSession().getPeerHost(), engine.getSession().getPeerPort(),
TimeUnit.SECONDS.toMillis(creationTime), TimeUnit.SECONDS.toMillis(timeout),
quicSession, isSingleUse);
}
}

// Mimic the encoding of quiche: https://github.com/cloudflare/quiche/blob/0.10.0/src/lib.rs#L1668
private static byte[] toQuicheQuicSession(byte[] sslSession, byte[] peerParams) {
if (sslSession != null && peerParams != null) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos)) {
dos.writeLong(sslSession.length);
dos.write(sslSession);
dos.writeLong(peerParams.length);
dos.write(peerParams);
return bos.toByteArray();
} catch (IOException e) {
return null;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.incubator.codec.quic;

/**
* Implementations of this interface can be used to send early data for a {@link QuicChannel}.
*/
@FunctionalInterface
public interface EarlyDataSendCallback {
/**
* Allow to send early-data if possible. Please be aware that early data may be replayable and so may have other
* security concerns then other data.
*
* @param quicChannel the {@link QuicChannel} which will be used to send data on (if any).
*/
void send(QuicChannel quicChannel);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class QuicChannelBootstrap {
private QuicConnectionAddress connectionAddress = QuicConnectionAddress.EPHEMERAL;
private ChannelHandler handler;
private ChannelHandler streamHandler;
private EarlyDataSendCallback earlyDataSendCallback;

/**
* Creates a new instance which uses the given {@link Channel} to bootstrap the {@link QuicChannel}.
Expand Down Expand Up @@ -168,6 +169,17 @@ public QuicChannelBootstrap connectionAddress(QuicConnectionAddress connectionAd
return this;
}

/**
* Set the {@link EarlyDataSendCallback} to use.
*
* @param earlyDataSendCallback the callback.
* @return this instance.
*/
public QuicChannelBootstrap earlyDataSendCallBack(EarlyDataSendCallback earlyDataSendCallback) {
this.earlyDataSendCallback = ObjectUtil.checkNotNull(earlyDataSendCallback, "earlyDataSendCallback");
return this;
}

/**
* Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
*
Expand Down Expand Up @@ -197,7 +209,8 @@ public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
}
final QuicConnectionAddress address = connectionAddress;
QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress) remote,
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs),
earlyDataSendCallback);

Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
EventLoop eventLoop = parent.eventLoop();
Expand Down
Loading

0 comments on commit 07d872f

Please sign in to comment.