#8695: fix inconsistencies between quiche's native API and its JNA/Foreign bindings

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2022-10-11 10:54:06 +02:00
parent d6a101d463
commit 15e90acab6
11 changed files with 85 additions and 40 deletions

View File

@ -21,6 +21,7 @@ import java.net.SocketException;
import java.net.SocketOption; import java.net.SocketOption;
import java.net.StandardProtocolFamily; import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions; import java.net.StandardSocketOptions;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NetworkChannel; import java.nio.channels.NetworkChannel;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
@ -501,6 +502,13 @@ public class ClientConnector extends ContainerLifeCycle
if (sendBufferSize >= 0) if (sendBufferSize >= 0)
setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize); setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize);
} }
if (selectable instanceof DatagramChannel)
{
// QUIC must know the local address, but it is non-null on datagram sockets only if it has been bound,
// so implicitly bind to 0.0.0.0:0 when no bind address has been set.
if (getBindAddress() == null)
setBindAddress(new InetSocketAddress("0.0.0.0", 0));
}
} }
private <T> void setSocketOption(NetworkChannel channel, SocketOption<T> option, T value) private <T> void setSocketOption(NetworkChannel channel, SocketOption<T> option, T value)

View File

@ -61,6 +61,43 @@
<artifactId>quic-quiche-foreign-incubator</artifactId> <artifactId>quic-quiche-foreign-incubator</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<!-- Make sure to use the Foreign binding by adding and opening the jdk.incubator.foreign module. -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
@{argLine}
${jetty.surefire.argLine}
--add-modules=jdk.incubator.foreign
--add-opens jdk.incubator.foreign/jdk.incubator.foreign=ALL-UNNAMED
--enable-native-access ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!-- Don't use the Foreign binding if the JDK version != 17. -->
<id>jdk18+</id>
<activation>
<jdk>[18,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
@{argLine}
${jetty.surefire.argLine}
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@ -96,7 +96,7 @@ public class ClientQuicConnection extends QuicConnection
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("connecting to {} with protocols {}", remoteAddress, protocols); LOG.debug("connecting to {} with protocols {}", remoteAddress, protocols);
QuicheConnection quicheConnection = QuicheConnection.connect(quicheConfig, remoteAddress); QuicheConnection quicheConnection = QuicheConnection.connect(quicheConfig, getEndPoint().getLocalAddress(), remoteAddress);
ClientQuicSession session = new ClientQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, remoteAddress, context); ClientQuicSession session = new ClientQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, remoteAddress, context);
pendingSessions.put(remoteAddress, session); pendingSessions.put(remoteAddress, session);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())

View File

@ -24,7 +24,7 @@ public interface QuicheBinding
int priority(); int priority();
byte[] fromPacket(ByteBuffer packet); byte[] fromPacket(ByteBuffer packet);
QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException; QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException;
boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException; boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException;
QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException; QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException;
} }

View File

@ -49,14 +49,14 @@ public abstract class QuicheConnection
LOG.debug("using quiche binding implementation: {}", QUICHE_BINDING.getClass().getName()); LOG.debug("using quiche binding implementation: {}", QUICHE_BINDING.getClass().getName());
} }
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer) throws IOException public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer) throws IOException
{ {
return connect(quicheConfig, peer, Quiche.QUICHE_MAX_CONN_ID_LEN); return connect(quicheConfig, local, peer, Quiche.QUICHE_MAX_CONN_ID_LEN);
} }
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
{ {
return QUICHE_BINDING.connect(quicheConfig, peer, connectionIdLength); return QUICHE_BINDING.connect(quicheConfig, local, peer, connectionIdLength);
} }
/** /**
@ -73,9 +73,9 @@ public abstract class QuicheConnection
* Fully consumes the {@code packetRead} buffer if the connection was accepted. * Fully consumes the {@code packetRead} buffer if the connection was accepted.
* @return an established connection if accept succeeded, null if accept failed and negotiation should be tried. * @return an established connection if accept succeeded, null if accept failed and negotiation should be tried.
*/ */
public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
{ {
return QUICHE_BINDING.tryAccept(quicheConfig, tokenValidator, packetRead, peer); return QUICHE_BINDING.tryAccept(quicheConfig, tokenValidator, packetRead, local, peer);
} }
public final List<Long> readableStreamIds() public final List<Long> readableStreamIds()

View File

@ -57,9 +57,9 @@ public class ForeignIncubatorQuicheBinding implements QuicheBinding
} }
@Override @Override
public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
{ {
return ForeignIncubatorQuicheConnection.connect(quicheConfig, peer, connectionIdLength); return ForeignIncubatorQuicheConnection.connect(quicheConfig, local, peer, connectionIdLength);
} }
@Override @Override
@ -69,9 +69,9 @@ public class ForeignIncubatorQuicheBinding implements QuicheBinding
} }
@Override @Override
public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
{ {
return ForeignIncubatorQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, peer); return ForeignIncubatorQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, local, peer);
} }
@Override @Override

View File

@ -746,8 +746,8 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{ {
if (quicheConn == null) if (quicheConn == null)
throw new IllegalStateException("connection was released"); throw new IllegalStateException("connection was released");
quiche_h.quiche_conn_path_stats(quicheConn, pathStats.address()); quiche_h.quiche_conn_path_stats(quicheConn, 0L, pathStats.address());
return quiche_path_stats.get_cwnd(stats); return quiche_path_stats.get_cwnd(pathStats);
} }
} }

View File

@ -760,11 +760,11 @@ public class quiche_h
} }
} }
public static int quiche_conn_path_stats(MemoryAddress conn, MemoryAddress stats) public static int quiche_conn_path_stats(MemoryAddress conn, long idx, MemoryAddress stats)
{ {
try try
{ {
return (int)quiche_conn_path_stats$MH.invokeExact(conn, stats); return (int)quiche_conn_path_stats$MH.invokeExact(conn, idx, stats);
} }
catch (Throwable ex) catch (Throwable ex)
{ {

View File

@ -57,9 +57,9 @@ public class JnaQuicheBinding implements QuicheBinding
} }
@Override @Override
public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
{ {
return JnaQuicheConnection.connect(quicheConfig, peer, connectionIdLength); return JnaQuicheConnection.connect(quicheConfig, local, peer, connectionIdLength);
} }
@Override @Override
@ -69,9 +69,9 @@ public class JnaQuicheBinding implements QuicheBinding
} }
@Override @Override
public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
{ {
return JnaQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, peer); return JnaQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, local, peer);
} }
@Override @Override

View File

@ -231,54 +231,54 @@ public interface LibQuiche extends Library
class quiche_path_stats extends Structure class quiche_path_stats extends Structure
{ {
// The local address used by this path. // The local address used by this path.
sockaddr_storage local_addr; public sockaddr_storage local_addr;
size_t local_addr_len; public size_t local_addr_len;
// The peer address seen by this path. // The peer address seen by this path.
sockaddr_storage peer_addr; public sockaddr_storage peer_addr;
size_t peer_addr_len; public size_t peer_addr_len;
// The validation state of the path. // The validation state of the path.
ssize_t validation_state; public ssize_t validation_state;
// Whether this path is active. // Whether this path is active.
boolean active; public boolean active;
// The number of QUIC packets received on this path. // The number of QUIC packets received on this path.
size_t recv; public size_t recv;
// The number of QUIC packets sent on this path. // The number of QUIC packets sent on this path.
size_t sent; public size_t sent;
// The number of QUIC packets that were lost on this path. // The number of QUIC packets that were lost on this path.
size_t lost; public size_t lost;
// The number of sent QUIC packets with retransmitted data on this path. // The number of sent QUIC packets with retransmitted data on this path.
size_t retrans; public size_t retrans;
// The estimated round-trip time of the path (in nanoseconds). // The estimated round-trip time of the path (in nanoseconds).
uint64_t rtt; public uint64_t rtt;
// The size of the path's congestion window in bytes. // The size of the path's congestion window in bytes.
size_t cwnd; public size_t cwnd;
// The number of sent bytes on this path. // The number of sent bytes on this path.
uint64_t sent_bytes; public uint64_t sent_bytes;
// The number of received bytes on this path. // The number of received bytes on this path.
uint64_t recv_bytes; public uint64_t recv_bytes;
// The number of bytes lost on this path. // The number of bytes lost on this path.
uint64_t lost_bytes; public uint64_t lost_bytes;
// The number of stream bytes retransmitted on this path. // The number of stream bytes retransmitted on this path.
uint64_t stream_retrans_bytes; public uint64_t stream_retrans_bytes;
// The current PMTU for the path. // The current PMTU for the path.
size_t pmtu; public size_t pmtu;
// The most recent data delivery rate estimate in bytes/s. // The most recent data delivery rate estimate in bytes/s.
uint64_t delivery_rate; public uint64_t delivery_rate;
} }
interface LoggingCallback extends Callback interface LoggingCallback extends Callback

View File

@ -62,7 +62,7 @@ public class ServerQuicConnection extends QuicConnection
{ {
ByteBufferPool byteBufferPool = getByteBufferPool(); ByteBufferPool byteBufferPool = getByteBufferPool();
// TODO make the token validator configurable // TODO make the token validator configurable
QuicheConnection quicheConnection = QuicheConnection.tryAccept(connector.newQuicheConfig(), new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, remoteAddress); QuicheConnection quicheConnection = QuicheConnection.tryAccept(connector.newQuicheConfig(), new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, getEndPoint().getLocalAddress(), remoteAddress);
if (quicheConnection == null) if (quicheConnection == null)
{ {
ByteBuffer negotiationBuffer = byteBufferPool.acquire(getOutputBufferSize(), true); ByteBuffer negotiationBuffer = byteBufferPool.acquire(getOutputBufferSize(), true);