now capable of accepting a quic connection
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
parent
1f588d719d
commit
e347074975
|
@ -24,6 +24,11 @@
|
|||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>cloudflare-quiche-jna</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<QuicheConnectionId, QuicheConnection> 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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue