Skip to content

Commit 07d872f

Browse files
Add zero rtt support for client (java-native-access#362)
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]>
1 parent 7f597c2 commit 07d872f

17 files changed

Lines changed: 847 additions & 32 deletions

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSL.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ static long SSLContext_new(boolean server, String[] applicationProtocols,
4343
BoringSSLCertificateVerifyCallback verifyCallback,
4444
BoringSSLTlsextServernameCallback servernameCallback,
4545
BoringSSLKeylogCallback keylogCallback,
46+
BoringSSLSessionCallback sessionCallback,
4647
int verifyMode,
4748
byte[][] subjectNames) {
4849
return SSLContext_new0(server, toWireFormat(applicationProtocols),
49-
handshakeCompleteCallback,
50-
certificateCallback, verifyCallback, servernameCallback, keylogCallback, verifyMode, subjectNames);
50+
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
51+
keylogCallback, sessionCallback, verifyMode, subjectNames);
5152
}
5253

5354
private static byte[] toWireFormat(String[] applicationProtocols) {
@@ -70,6 +71,7 @@ private static native long SSLContext_new0(boolean server,
7071
byte[] applicationProtocols, Object handshakeCompleteCallback,
7172
Object certificateCallback, Object verifyCallback,
7273
Object servernameCallback, Object keylogCallback,
74+
Object sessionCallback,
7375
int verifyDepth, byte[][] subjectNames);
7476
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
7577
static native long SSLContext_setSessionCacheSize(long context, long size);

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSLHandshakeCompleteCallback.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class BoringSSLHandshakeCompleteCallback {
2626
@SuppressWarnings("unused")
2727
void handshakeComplete(long ssl, byte[] id, String cipher, String protocol, byte[] peerCertificate,
2828
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol) {
29-
QuicheQuicSslEngine engine = map.remove(ssl);
29+
QuicheQuicSslEngine engine = map.get(ssl);
3030
if (engine != null) {
3131
engine.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime,
3232
timeout, applicationProtocol);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2021 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.incubator.codec.quic;
17+
18+
import io.netty.util.internal.EmptyArrays;
19+
import io.netty.util.internal.logging.InternalLogger;
20+
import io.netty.util.internal.logging.InternalLoggerFactory;
21+
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.DataOutputStream;
24+
import java.io.IOException;
25+
import java.util.Arrays;
26+
import java.util.concurrent.TimeUnit;
27+
28+
final class BoringSSLSessionCallback {
29+
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLSessionCallback.class);
30+
private final QuicClientSessionCache sessionCache;
31+
private final QuicheQuicSslEngineMap engineMap;
32+
33+
BoringSSLSessionCallback(QuicheQuicSslEngineMap engineMap, QuicClientSessionCache sessionCache) {
34+
this.engineMap = engineMap;
35+
this.sessionCache = sessionCache;
36+
}
37+
38+
@SuppressWarnings("unused")
39+
void newSession(long ssl, long creationTime, long timeout, byte[] session, boolean isSingleUse, byte[] peerParams) {
40+
if (sessionCache == null) {
41+
return;
42+
}
43+
44+
QuicheQuicSslEngine engine = engineMap.get(ssl);
45+
if (engine == null) {
46+
logger.warn("engine is null ssl: {}", ssl);
47+
return;
48+
}
49+
50+
if (peerParams == null) {
51+
peerParams = EmptyArrays.EMPTY_BYTES;
52+
}
53+
if (logger.isDebugEnabled()) {
54+
logger.debug("ssl: {}, session: {}, peerParams: {}", ssl, Arrays.toString(session),
55+
Arrays.toString(peerParams));
56+
}
57+
byte[] quicSession = toQuicheQuicSession(session, peerParams);
58+
if (quicSession != null) {
59+
logger.debug("save session host={}, port={}",
60+
engine.getSession().getPeerHost(), engine.getSession().getPeerPort());
61+
sessionCache.saveSession(engine.getSession().getPeerHost(), engine.getSession().getPeerPort(),
62+
TimeUnit.SECONDS.toMillis(creationTime), TimeUnit.SECONDS.toMillis(timeout),
63+
quicSession, isSingleUse);
64+
}
65+
}
66+
67+
// Mimic the encoding of quiche: https://github.com/cloudflare/quiche/blob/0.10.0/src/lib.rs#L1668
68+
private static byte[] toQuicheQuicSession(byte[] sslSession, byte[] peerParams) {
69+
if (sslSession != null && peerParams != null) {
70+
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
71+
DataOutputStream dos = new DataOutputStream(bos)) {
72+
dos.writeLong(sslSession.length);
73+
dos.write(sslSession);
74+
dos.writeLong(peerParams.length);
75+
dos.write(peerParams);
76+
return bos.toByteArray();
77+
} catch (IOException e) {
78+
return null;
79+
}
80+
}
81+
return null;
82+
}
83+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2021 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.incubator.codec.quic;
17+
18+
/**
19+
* Implementations of this interface can be used to send early data for a {@link QuicChannel}.
20+
*/
21+
@FunctionalInterface
22+
public interface EarlyDataSendCallback {
23+
/**
24+
* Allow to send early-data if possible. Please be aware that early data may be replayable and so may have other
25+
* security concerns then other data.
26+
*
27+
* @param quicChannel the {@link QuicChannel} which will be used to send data on (if any).
28+
*/
29+
void send(QuicChannel quicChannel);
30+
}

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicChannelBootstrap.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public final class QuicChannelBootstrap {
5050
private QuicConnectionAddress connectionAddress = QuicConnectionAddress.EPHEMERAL;
5151
private ChannelHandler handler;
5252
private ChannelHandler streamHandler;
53+
private EarlyDataSendCallback earlyDataSendCallback;
5354

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

172+
/**
173+
* Set the {@link EarlyDataSendCallback} to use.
174+
*
175+
* @param earlyDataSendCallback the callback.
176+
* @return this instance.
177+
*/
178+
public QuicChannelBootstrap earlyDataSendCallBack(EarlyDataSendCallback earlyDataSendCallback) {
179+
this.earlyDataSendCallback = ObjectUtil.checkNotNull(earlyDataSendCallback, "earlyDataSendCallback");
180+
return this;
181+
}
182+
171183
/**
172184
* Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
173185
*
@@ -197,7 +209,8 @@ public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
197209
}
198210
final QuicConnectionAddress address = connectionAddress;
199211
QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress) remote,
200-
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
212+
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs),
213+
earlyDataSendCallback);
201214

202215
Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
203216
EventLoop eventLoop = parent.eventLoop();

0 commit comments

Comments
 (0)