diff --git a/jetty-http3/http3-server/pom.xml b/jetty-http3/http3-server/pom.xml
index f826fc15a2b..4f4d6186734 100644
--- a/jetty-http3/http3-server/pom.xml
+++ b/jetty-http3/http3-server/pom.xml
@@ -24,6 +24,11 @@
jetty-server
${project.version}
+
+ org.eclipse.jetty.http3
+ cloudflare-quiche-jna
+ ${project.version}
+
org.eclipse.jetty
jetty-slf4j-impl
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/DatagramAdaptingEndPoint.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/DatagramAdaptingEndPoint.java
index 118c19db24f..1cab6a68fab 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/DatagramAdaptingEndPoint.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/DatagramAdaptingEndPoint.java
@@ -75,10 +75,7 @@ public class DatagramAdaptingEndPoint implements EndPoint
if (filled == 0)
return 0;
- int headerPosition = buffer.position();
remoteAddress = ServerDatagramEndPoint.decodeInetSocketAddress(buffer);
- buffer.position(headerPosition + ServerDatagramEndPoint.ENCODED_ADDRESS_LENGTH);
-
return filled;
}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/QuicConnection.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/QuicConnection.java
index ca05038f7d5..a75c89dc2d4 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/QuicConnection.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/QuicConnection.java
@@ -1,81 +1,133 @@
package org.eclipse.jetty.http3.server;
-import java.util.EventListener;
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.http3.quiche.QuicheConfig;
+import org.eclipse.jetty.http3.quiche.QuicheConnection;
+import org.eclipse.jetty.http3.quiche.QuicheConnectionId;
+import org.eclipse.jetty.http3.quiche.ffi.LibQuiche;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class QuicConnection implements Connection
+public class QuicConnection extends AbstractConnection
{
- @Override
- public void addEventListener(EventListener listener)
- {
+ private static final Logger LOG = LoggerFactory.getLogger(QuicConnection.class);
+ private final ByteBufferPool byteBufferPool;
+ private final ConcurrentMap connections = new ConcurrentHashMap<>();
+ private final QuicheConfig quicheConfig;
+
+ public QuicConnection(ByteBufferPool byteBufferPool, Executor executor, ServerDatagramEndPoint endp)
+ {
+ super(endp, executor);
+ this.byteBufferPool = byteBufferPool;
+
+ File[] files;
+ try
+ {
+ SSLKeyPair keyPair;
+ keyPair = new SSLKeyPair(new File("src/test/resources/keystore.p12"), "PKCS12", "storepwd".toCharArray(), "mykey", "storepwd".toCharArray());
+ files = keyPair.export(new File(System.getProperty("java.io.tmpdir")));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ quicheConfig = new QuicheConfig();
+ quicheConfig.setPrivKeyPemPath(files[0].getPath());
+ quicheConfig.setCertChainPemPath(files[1].getPath());
+ quicheConfig.setVerifyPeer(false);
+ quicheConfig.setMaxIdleTimeout(5000L);
+ quicheConfig.setInitialMaxData(10000000L);
+ quicheConfig.setInitialMaxStreamDataBidiLocal(10000000L);
+ quicheConfig.setInitialMaxStreamDataBidiRemote(10000000L);
+ quicheConfig.setInitialMaxStreamDataUni(10000000L);
+ quicheConfig.setInitialMaxStreamsBidi(100L);
+ quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.RENO);
+ quicheConfig.setApplicationProtos(getProtocols().toArray(new String[0]));
}
- @Override
- public void removeEventListener(EventListener listener)
+ private Collection getProtocols()
{
-
+ return Arrays.asList("http/0.9");
}
@Override
public void onOpen()
{
-
+ super.onOpen();
+ fillInterested();
}
@Override
- public void onClose(Throwable cause)
+ public void onFillable()
{
+ try
+ {
+ ByteBuffer buffer = byteBufferPool.acquire(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN + ServerDatagramEndPoint.ENCODED_ADDRESS_LENGTH, true);
+ // Read data
+ int fill = getEndPoint().fill(buffer);
+ if (fill < 0)
+ {
+ byteBufferPool.release(buffer);
+ getEndPoint().shutdownOutput();
+ return;
+ }
+ if (fill == 0)
+ {
+ byteBufferPool.release(buffer);
+ fillInterested();
+ return;
+ }
- }
+ InetSocketAddress remoteAddress = ServerDatagramEndPoint.decodeInetSocketAddress(buffer);
+ QuicheConnectionId quicheConnectionId = QuicheConnectionId.fromPacket(buffer);
+ QuicheConnection quicheConnection = connections.get(quicheConnectionId);
+ if (quicheConnection == null)
+ {
+ quicheConnection = QuicheConnection.tryAccept(quicheConfig, remoteAddress, buffer);
+ if (quicheConnection == null)
+ {
+ ByteBuffer address = byteBufferPool.acquire(ServerDatagramEndPoint.ENCODED_ADDRESS_LENGTH, true);
+ BufferUtil.flipToFill(address);
+ ServerDatagramEndPoint.encodeInetSocketAddress(address, remoteAddress);
+ address.flip();
- @Override
- public EndPoint getEndPoint()
- {
- return null;
- }
+ ByteBuffer buffer2 = byteBufferPool.acquire(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN, true);
+ BufferUtil.flipToFill(buffer2);
+ QuicheConnection.negotiate(remoteAddress, buffer, buffer2);
- @Override
- public void close()
- {
-
- }
-
- @Override
- public boolean onIdleExpired()
- {
- return false;
- }
-
- @Override
- public long getMessagesIn()
- {
- return 0;
- }
-
- @Override
- public long getMessagesOut()
- {
- return 0;
- }
-
- @Override
- public long getBytesIn()
- {
- return 0;
- }
-
- @Override
- public long getBytesOut()
- {
- return 0;
- }
-
- @Override
- public long getCreatedTimeStamp()
- {
- return 0;
+ getEndPoint().flush(address, buffer2);
+ byteBufferPool.release(address);
+ byteBufferPool.release(buffer2);
+ }
+ else
+ {
+ LOG.info("Quic connection accepted");
+ connections.put(quicheConnectionId, quicheConnection);
+ }
+ }
+ else
+ {
+ quicheConnection.feedCipherText(buffer);
+ }
+ byteBufferPool.release(buffer);
+ fillInterested();
+ }
+ catch (Throwable x)
+ {
+ close();
+ }
}
}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/SSLKeyPair.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/SSLKeyPair.java
new file mode 100644
index 00000000000..3b5948d0028
--- /dev/null
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/SSLKeyPair.java
@@ -0,0 +1,105 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.server;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.util.Base64;
+
+public class SSLKeyPair
+{
+ private static final String BEGIN_KEY = "-----BEGIN PRIVATE KEY-----";
+ private static final String END_KEY = "-----END PRIVATE KEY-----";
+ private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
+ private static final String END_CERT = "-----END CERTIFICATE-----";
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+ private static final int LINE_LENGTH = 64;
+
+ private final Key key;
+ private final Certificate cert;
+ private final String alias;
+
+ public SSLKeyPair(Key key, Certificate cert, String alias)
+ {
+ this.key = key;
+ this.cert = cert;
+ this.alias = alias;
+ }
+
+ public SSLKeyPair(File storeFile, String storeType, char[] storePassword, String alias, char[] keyPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException
+ {
+ KeyStore keyStore = KeyStore.getInstance(storeType);
+ try (FileInputStream fis = new FileInputStream(storeFile))
+ {
+ keyStore.load(fis, storePassword);
+ this.alias = alias;
+ this.key = keyStore.getKey(alias, keyPassword);
+ this.cert = keyStore.getCertificate(alias);
+ }
+ }
+
+ /**
+ * @return [0] is the key file, [1] is the cert file.
+ */
+ public File[] export(File targetFolder) throws Exception
+ {
+ File[] files = new File[2];
+ files[0] = new File(targetFolder, alias + ".key");
+ files[1] = new File(targetFolder, alias + ".crt");
+
+ try (FileOutputStream fos = new FileOutputStream(files[0]))
+ {
+ writeAsPem(fos, key);
+ }
+ try (FileOutputStream fos = new FileOutputStream(files[1]))
+ {
+ writeAsPem(fos, cert);
+ }
+ return files;
+ }
+
+ private void writeAsPem(OutputStream outputStream, Key key) throws IOException
+ {
+ Base64.Encoder encoder = Base64.getMimeEncoder(LINE_LENGTH, LINE_SEPARATOR.getBytes());
+ byte[] encoded = encoder.encode(key.getEncoded());
+ outputStream.write(BEGIN_KEY.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(encoded);
+ outputStream.write(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(END_KEY.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static void writeAsPem(OutputStream outputStream, Certificate certificate) throws CertificateEncodingException, IOException
+ {
+ Base64.Encoder encoder = Base64.getMimeEncoder(LINE_LENGTH, LINE_SEPARATOR.getBytes());
+ byte[] encoded = encoder.encode(certificate.getEncoded());
+ outputStream.write(BEGIN_CERT.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(encoded);
+ outputStream.write(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));
+ outputStream.write(END_CERT.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramConnector.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramConnector.java
index 4c226c69bd8..edb5f89f18b 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramConnector.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramConnector.java
@@ -10,7 +10,6 @@ import java.nio.channels.Selector;
import java.util.EventListener;
import java.util.concurrent.Executor;
-import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -18,8 +17,6 @@ import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.server.AbstractNetworkConnector;
import org.eclipse.jetty.server.ConnectionFactory;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.annotation.Name;
@@ -170,11 +167,7 @@ public class ServerDatagramConnector extends AbstractNetworkConnector
@Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
- //TODO: return quic connection
- //return new QuicConnection();
- HttpConfiguration config = new HttpConfiguration();
- config.setHttpCompliance(HttpCompliance.LEGACY); // enable HTTP/0.9
- return new HttpConnection(config, ServerDatagramConnector.this, new DatagramAdaptingEndPoint((ServerDatagramEndPoint)endpoint), false);
+ return new QuicConnection(getByteBufferPool(), getExecutor(), (ServerDatagramEndPoint)endpoint);
}
@Override
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramEndPoint.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramEndPoint.java
index b360cc30b5a..700a55f9a70 100644
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramEndPoint.java
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/ServerDatagramEndPoint.java
@@ -253,6 +253,7 @@ public class ServerDatagramEndPoint extends IdleTimeout implements EndPoint, Man
static InetSocketAddress decodeInetSocketAddress(ByteBuffer buffer) throws IOException
{
+ int headerPosition = buffer.position();
byte ipVersion = buffer.get();
byte[] address;
if (ipVersion == 4)
@@ -262,11 +263,13 @@ public class ServerDatagramEndPoint extends IdleTimeout implements EndPoint, Man
else throw new IOException("Unsupported IP version: " + ipVersion);
buffer.get(address);
int port = buffer.getChar();
+ buffer.position(headerPosition + ENCODED_ADDRESS_LENGTH);
return new InetSocketAddress(InetAddress.getByAddress(address), port);
}
static void encodeInetSocketAddress(ByteBuffer buffer, InetSocketAddress peer) throws IOException
{
+ int headerPosition = buffer.position();
byte[] addressBytes = peer.getAddress().getAddress();
int port = peer.getPort();
byte ipVersion;
@@ -279,5 +282,6 @@ public class ServerDatagramEndPoint extends IdleTimeout implements EndPoint, Man
buffer.put(ipVersion);
buffer.put(addressBytes);
buffer.putChar((char)port);
+ buffer.position(headerPosition + ENCODED_ADDRESS_LENGTH);
}
}
diff --git a/jetty-http3/http3-server/src/test/resources/keystore.p12 b/jetty-http3/http3-server/src/test/resources/keystore.p12
new file mode 100644
index 00000000000..0b56dd34ee9
Binary files /dev/null and b/jetty-http3/http3-server/src/test/resources/keystore.p12 differ