-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: [QUIC] QuicListener #67560
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationAPI design for exposing There are 2 major design approaches. The first one requires options for incoming connection (to finish the handshake) during listener creation. The alternative expects these options not until Options UpfrontThis design was discussed in our team and is based on what we have now. Thus, it is safer since we have at least some experience with this shape.
Options in AcceptThis design came out from my experiments with listener shape. It's been prototyped but it's unproven design.
Related issues:
API Proposal// Change from the current class is removal of all the constructors which are replaced with static Create method.
// All the Quic classes will have static Create so that they can became abstract in the future.
// Also all the overloads accepting implementation provider (temporary) are removed.
public class QuicListener : IAsyncDisposable
{
// Static create method instead of ctor so that the class can became abstract in the future.
public static QuicListener Create(QuicListenerOptions options);
public IPEndPoint ListenEndPoint { get; }
public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default);
public void DisposeAsync();
}
// The state object is questionable here since there's no way to provide one per accepted connection in this design.
public delegate ValueTask<SslServerAuthenticationOptions> ServerOptionsSelectionCallback(QuicConnection connection, object? state, SslClientHelloInfo clientHelloInfo, CancellationToken cancellationToken);
public class QuicListenerOptions
{
// Listener options necessary for starting a listener.
[Required]
public IPEndPoint ListenEndPoint { get; init; }
[Required]
public List<SslApplicationProtocol> ApplicationProtocols { get; init; }
public int ListenBacklog { get; init; } = 512;
// Options for incoming connections.
[Required]
public QuicConnectionOptions ConnectionOptions { get; init; }
// SSL options for incoming connections, either one of the following must be defined.
// Direct options, no callback will be invoked if provided.
public SslServerAuthenticationOptions? ServerSslOptions { get; init; }
// Callback instead of direct options, invoked only if ServerSslOptions not specified.
public ServerOptionsSelectionCallback? ServerSslOptionsSelectionCallback { get; init; }
// Optional state for ServerSslOptionsSelectionCallback. Might not make sense if it's just one object per listener.
public object? ServerSslOptionsSelectionCallbackState { get; init; }
}
/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>
public class QuicConnectionOptions
{
/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>
public int MaxBidirectionalStreams { get; init; } = 100;
/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>
public int MaxUnidirectionalStreams { get; init; } = 100;
/// <summary>Idle timeout for connections, after which the connection will be closed.</summary>
public TimeSpan IdleTimeout { get; init; } = TimeSpan.FromMinutes(2);
// This class will potentially expand with other connection options which we'll deem interesting for user to set.
} API UsageOptions Upfrontvar serverSslOptions = new SslServerAuthenticationOptions() {
ApplicationProtocols = new List<SslApplicationProtocol>(){ SslApplicationProtocol.Http3 },
ServerCertificate = TestCertificateExtensions.ServerCertificate
};
await using var listener = MsQuicListener.Create(new QuicListenerOptions() {
ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 5000),
ApplicationProtocols = { SslApplicationProtocol.Http3 },
ListenBacklog = 1024,
ConnectionOptions = new QuicConnectionOptions() {
IdleTimeout = TimeSpan.FromMinutes(5),
MaxBidirectionalStreams = 1000,
MaxUnidirectionalStreams = 10
},
// Either this
ServerSslOptions = serverSslOptions,
// Or these 2
ServerSslOptionsSelectionCallback = (connection, clientHelloInfo, state, cancellationToken) => {
// ...
return new ValueTask<SslServerAuthenticationOptions>(serverSslOptions);
},
ServerSslOptionsSelectionCallbackState = null,
});
while (running) {
await using var connection = await listener.AcceptConnectionAsync(cancellationToken);
// More work with connection
} Options in Acceptawait using var listener = MsQuicListener.Create(new QuicListenerOptions() {
ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 5000),
ApplicationProtocols = { SslApplicationProtocol.Http3 },
ListenBacklog = 1024,
});
var connectionOptions = new QuicConnectionOptions() {
IdleTimeout = TimeSpan.FromMinutes(5),
MaxBidirectionalStreams = 1000,
MaxUnidirectionalStreams = 10
},
var serverSslOptions = new SslServerAuthenticationOptions() {
ApplicationProtocols = new List<SslApplicationProtocol>(){ SslApplicationProtocol.Http3 },
ServerCertificate = TestCertificateExtensions.ServerCertificate
};
var serverSslOptionsSelectionCallback = (connection, clientHelloInfo, state, cancellationToken) => {
// ...
return new ValueTask<SslServerAuthenticationOptions>(serverSslOptions);
};
while (running) {
// Either this way
await using var connection = await listener.AcceptConnectionAsync(serverSslOptions, connectionOptions, cancellationToken);
// Or that way
await using var connection = await listener.AcceptConnectionAsync(serverSslOptionsSelectionCallback, null, connectionOptions, cancellationToken);
// More work with connection
} Alternative Designs/// <summary>Represents server side of QUIC transport. Listens for incoming QuicConnections.</summary>
public sealed class QuicListener : IAsyncDisposable
{
// Static create method instead of ctor so that the class can became abstract in the future.
public static QuicListener Create(QuicListenerOptions options);
public IPEndPoint ListenEndPoint;
// Accept methods now have SSL and connection options as extra arguments.
// Those options are connection specific and there's no need to provide them at the time of listener creation.
// This also allows us to pass sslOptionsCallbackState
public ValueTask<QuicConnection> AcceptConnectionAsync(ServerSslOptionsSelectionCallback sslOptionsCallback, object? sslOptionsCallbackState, QuicConnectionOptions connectionOptions, CancellationToken cancellationToken = default);
public ValueTask<QuicConnection> AcceptConnectionAsync(SslServerAuthenticationOptions sslOptions, QuicConnectionOptions connectionOptions, CancellationToken cancellationToken = default);
public ValueTask DisposeAsync();
}
public delegate ValueTask<SslServerAuthenticationOptions> ServerSslOptionsSelectionCallback(QuicConnection connection, object? state, SslClientHelloInfo clientHelloInfo, CancellationToken cancellationToken);
public class QuicListenerOptions
{
/// <summary>The local endpoint to listen on.</summary>
[Required]
public IPEndPoint ListenEndPoint { get; init; } = null!;
/// <summary>The application protocols.</summary>
[Required]
public SslApplicationProtocol[] ApplicationProtocols { get; init; } = Array.Empty<SslApplicationProtocol>();
/// <summary>Number of connections to be held without accepting the connection.</summary>
public int ListenBacklog { get; init; } = 512;
}
/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>
public class QuicConnectionOptions
{
/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>
public int MaxBidirectionalStreams { get; init; } = 100;
/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>
public int MaxUnidirectionalStreams { get; init; } = 100;
/// <summary>Idle timeout for connections, after which the connection will be closed.</summary>
public TimeSpan IdleTimeout { get; init; } = TimeSpan.FromMinutes(2);
// This class will potentially expand with other connection options which we'll deem interesting for user to set.
} RisksAs I'll state with all QUIC APIs. We might consider making all of these
|
Why not integrate it into the existing |
QUIC is transport protocol on it's own. HTTP can use it (and it does in HTTP/3) but that's just one way to use it, there are others (e.g.: SMB over QUIC), including fully custom protocols based on QUIC. You can look at this more like sockets with some more features. |
I am conflicted about the duplication of More to the topic: I personally like the "specify |
In order to support multiple independent listeners (for different protocols, such as SMB and HTTP) on the same port, the listeners must preregister the ALPN(s) they are interested in upfront so the stack and multiplex between them. Think of this as the same as the local UDP port. You don't open a socket and get all local UDP ports on the system and then decide if you want to take a specific port or ports. You register it up front and then selectively get only what you need/want. |
Yeah the duplicate ALPN specification has been discussed with msquic and ASP.NET and we have reached a working compromise (microsoft/msquic#2418), this API takes that into account. |
|
Fixed, good catch. |
ASP.NET Core feedback:
Basically, the callback would start returning public class QuicConnectionOptions
{
public int MaxBidirectionalStreams { get; init; } = 100;
public int MaxUnidirectionalStreams { get; init; } = 100;
public TimeSpan IdleTimeout { get; init; } = TimeSpan.FromMinutes(2);
// Add TLS options to QuicConnectionOptions
public SslServerAuthenticationOptions ServerSslOptions { get; init; }
}
// Callback returns QuicConnectionOptions
public delegate ValueTask<QuicConnectionOptions> ServerOptionsSelectionCallback(QuicConnection connection, object? state, SslClientHelloInfo clientHelloInfo, CancellationToken cancellationToken);
public class QuicListenerOptions
{
// Either specify options once, or via a callback
public QuicConnectionOptions ConnectionOptions { get; init; }
public ServerOptionsSelectionCallback? ConnectionOptionsSelectionCallback { get; init; }
public object? ConnectionOptionsSelectionCallbackState { get; init; }
} I think this is actually a bit cleaner. The TLS options for a connection are no longer their own special thing and are associated with the rest of the QUIC connection options. |
👍
👍
Currently we don't send anything to the client and the client waits on connection idle timeout. We even finish the handshake before we decide to not to accept the connection. This is the result of cumulative changes/fixes in 6.0 without proper design and I'm not fond of the way it works now. Options to improve:
We cannot straightforwardly combine |
namespace System.Net.Quic;
public sealed class QuicListener : IAsyncDisposable
{
public static bool IsSupported { get; }
public static ValueTask<QuicListener> ListenAsync(QuicListenerOptions options,
CancellationToken cancellationToken = default);
public IPEndPoint ListenEndPoint { get; }
public ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default);
public ValueTask DisposeAsync();
}
public sealed class QuicListenerOptions
{
public QuicListenerOptions();
public IPEndPoint ListenEndPoint { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; }
public int ListenBacklog { get; set; }
public Func<QuicConnection, SslClientHelloInfo, CancellationToken, ValueTask<QuicServerConnectionOptions>> ConnectionOptionsCallback { get; set; }
} |
Background and motivation
API design for exposing
QuicListener
and related classes to the public.There are 2 major design approaches. The first one requires options for incoming connection (to finish the handshake) during listener creation. The alternative doesn't expect these options until
Accept
is called. Both solutions take into account #49587.Options Upfront
This design was discussed in our team and is based on what we have now. Thus it is safer since we have at least some experience with this shape.
ServerOptionsSelectionCallback
since it'd need to be provided upfront (one object per listener).Related issues:
Dynamic selection of SSL options via
ServerOptionsSelectionCallback
ALPN needs to be provided upfront
API Proposal
API Usage
Risks
As I'll state with all QUIC APIs. We might consider making all of these
PreviewFeature
. Not to deter user from using it, but to give us flexibility to tune the API shape based on customer feedback.We don't have many users now and we're mostly making these APIs based on what Kestrel needs, our limited experience with System.Net.Quic and my meager experiments with other QUIC implementations.
cc: @JamesNK @Tratcher @wfurt @CarnaViire @rzikm
EDIT: Hid Options in Accept since Options Upfront are the preferred design. Also removed
stateObject
for the callback.Alternative Designs
Static
QuicServerConnectionOptions
Apart from
ServerOptionsSelectionCallback
,QuicListenerOptions
could also have direct property forQuicServerConnectionOptions
, bypassing the callback. Since it can always be provided via the callback, there's no functional need for it at the moment. Also ASP.NET Core needs the callback so we don't even have a use for it now.QuicServerConnectionOptions
in AcceptThis design came out from my experiments with listener shape. It's been prototyped but it's unproven design.
ServerOptionsSelectionCallback
callback.API Proposal
API Usage
The text was updated successfully, but these errors were encountered: