[API Proposal]: [QUIC] QuicConnection #68902

Description

@ManickaP

Background and motivation

API design for exposing QuicConnection and related classes to the public.

The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

Related issues:

Discussed Considerations

  1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
    --> YES
  2. Create with endpoint parameters or put them into options?
    a) inside options in ConnectAsync fits better with Socket/SslStream
    --> YES, also keep them non-nullable and throw if not connected yet
    b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
    --> NO
  3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
    • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
    • the resolution cannot be done in Create, it would need to be done in ConnectAsync
      --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
  4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
    • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
    • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
      --> YES, options in ConnectAsync, Create is ctor replacement
  5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

API Proposal

namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

API Usage

Client usage:

varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

Server usage:

// Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

Alternative Designs

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.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    [API Proposal]: [QUIC] QuicConnection #68902

    Description

    @ManickaP

    Background and motivation

    API design for exposing QuicConnection and related classes to the public.

    The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

    Related issues:

    Discussed Considerations

    1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
      --> YES
    2. Create with endpoint parameters or put them into options?
      a) inside options in ConnectAsync fits better with Socket/SslStream
      --> YES, also keep them non-nullable and throw if not connected yet
      b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
      --> NO
    3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
      • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
      • the resolution cannot be done in Create, it would need to be done in ConnectAsync
        --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
    4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
      • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
      • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
        --> YES, options in ConnectAsync, Create is ctor replacement
    5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

    API Proposal

    namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

    API Usage

    Client usage:

    varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

    Server usage:

    // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

    Alternative Designs

    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.

    Metadata

    Metadata

    Assignees

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      [API Proposal]: [QUIC] QuicConnection #68902

      Description

      @ManickaP

      Background and motivation

      API design for exposing QuicConnection and related classes to the public.

      The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

      Related issues:

      Discussed Considerations

      1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
        --> YES
      2. Create with endpoint parameters or put them into options?
        a) inside options in ConnectAsync fits better with Socket/SslStream
        --> YES, also keep them non-nullable and throw if not connected yet
        b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
        --> NO
      3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
        • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
        • the resolution cannot be done in Create, it would need to be done in ConnectAsync
          --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
      4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
        • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
        • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
          --> YES, options in ConnectAsync, Create is ctor replacement
      5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

      API Proposal

      namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

      API Usage

      Client usage:

      varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

      Server usage:

      // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

      Alternative Designs

      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.

      Metadata

      Metadata

      Assignees

      Labels

      api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        [API Proposal]: [QUIC] QuicConnection #68902

        Description

        @ManickaP

        Background and motivation

        API design for exposing QuicConnection and related classes to the public.

        The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

        Related issues:

        Discussed Considerations

        1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
          --> YES
        2. Create with endpoint parameters or put them into options?
          a) inside options in ConnectAsync fits better with Socket/SslStream
          --> YES, also keep them non-nullable and throw if not connected yet
          b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
          --> NO
        3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
          • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
          • the resolution cannot be done in Create, it would need to be done in ConnectAsync
            --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
        4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
          • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
          • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
            --> YES, options in ConnectAsync, Create is ctor replacement
        5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

        API Proposal

        namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

        API Usage

        Client usage:

        varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

        Server usage:

        // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

        Alternative Designs

        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.

        Metadata

        Metadata

        Assignees

        Labels

        api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          [API Proposal]: [QUIC] QuicConnection #68902

          Description

          @ManickaP

          Background and motivation

          API design for exposing QuicConnection and related classes to the public.

          The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

          Related issues:

          Discussed Considerations

          1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
            --> YES
          2. Create with endpoint parameters or put them into options?
            a) inside options in ConnectAsync fits better with Socket/SslStream
            --> YES, also keep them non-nullable and throw if not connected yet
            b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
            --> NO
          3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
            • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
            • the resolution cannot be done in Create, it would need to be done in ConnectAsync
              --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
          4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
            • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
            • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
              --> YES, options in ConnectAsync, Create is ctor replacement
          5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

          API Proposal

          namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

          API Usage

          Client usage:

          varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

          Server usage:

          // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

          Alternative Designs

          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.

          Metadata

          Metadata

          Assignees

          Labels

          api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            [API Proposal]: [QUIC] QuicConnection #68902

            Description

            @ManickaP

            Background and motivation

            API design for exposing QuicConnection and related classes to the public.

            The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

            Related issues:

            Discussed Considerations

            1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
              --> YES
            2. Create with endpoint parameters or put them into options?
              a) inside options in ConnectAsync fits better with Socket/SslStream
              --> YES, also keep them non-nullable and throw if not connected yet
              b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
              --> NO
            3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
              • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
              • the resolution cannot be done in Create, it would need to be done in ConnectAsync
                --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
            4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
              • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
              • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
                --> YES, options in ConnectAsync, Create is ctor replacement
            5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

            API Proposal

            namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

            API Usage

            Client usage:

            varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

            Server usage:

            // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

            Alternative Designs

            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.

            Metadata

            Metadata

            Assignees

            Labels

            api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              [API Proposal]: [QUIC] QuicConnection #68902

              Description

              @ManickaP

              Background and motivation

              API design for exposing QuicConnection and related classes to the public.

              The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

              Related issues:

              Discussed Considerations

              1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
                --> YES
              2. Create with endpoint parameters or put them into options?
                a) inside options in ConnectAsync fits better with Socket/SslStream
                --> YES, also keep them non-nullable and throw if not connected yet
                b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
                --> NO
              3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
                • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
                • the resolution cannot be done in Create, it would need to be done in ConnectAsync
                  --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
              4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
                • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
                • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
                  --> YES, options in ConnectAsync, Create is ctor replacement
              5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

              API Proposal

              namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

              API Usage

              Client usage:

              varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

              Server usage:

              // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

              Alternative Designs

              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.

              Metadata

              Metadata

              Assignees

              Labels

              api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                [API Proposal]: [QUIC] QuicConnection #68902

                Description

                @ManickaP

                Background and motivation

                API design for exposing QuicConnection and related classes to the public.

                The API shape is based on the current internal shape of the class with the exception of merging the ConnectAsync into QuicProvider.CreateConnectionAsync.

                Related issues:

                Discussed Considerations

                1. StreamCount/OpenStream parametrize by stream type instead of sets of 2 methods
                  --> YES
                2. Create with endpoint parameters or put them into options?
                  a) inside options in ConnectAsync fits better with Socket/SslStream
                  --> YES, also keep them non-nullable and throw if not connected yet
                  b) inside ctor allows us to have RemoteEndPoint non-nullable and better aligns with incoming connections (they have both side address, but are not "configured" with options yet)
                  --> NO
                3. Endpoints are IP endpoints, need to consider on which level we'll do DNS since MsQuic has only crude resolution
                  • to able to do what Socket does, means that we need to create MsQuic connection object, try to connect it, let it fail, release it and then repeat for another IP
                  • the resolution cannot be done in Create, it would need to be done in ConnectAsync
                    --> Properties are IPEndPoint, provided is EndPoint that can also be DnsEndPoint
                4. Options in ConnectAsync - we don't need them past the connection establishement moment so putting them into Create doesn't make much sense
                  • we might consider collapsing Create + ConnectAsync (==> we might consider making QuicListenerCreate async as well)
                  • we might keep Create parameter less, put everything to options and let ConnectAsync deal with it, this is not consistent with QuicListener though (does it matter or not?)
                    --> YES, options in ConnectAsync, Create is ctor replacement
                5. Multiple connections scenario: we'll need something like TryOpenStreamAsync + WaitForStreamAsync (not prototyped yet)

                API Proposal

                namespaceSystem.Net.Quic;publicsealedclassQuicConnection:IAsyncDisposable{/// <summary>Returns true if QUIC is supported and can be used, e.g. msquic is present, high enough version of TLS is available etc.</summary>publicstaticboolIsSupported{get;}/// <summary>Creates new, fully connected connection configured with the provided options.</summary>/// <exception cref="PlatformNotSupportedException">When <see cref="IsSupported" /> is <c>false</c>.</exception>publicstaticValueTask<QuicConnection>ConnectAsync(QuicConnectionOptionsoptions,CancellationTokencancellationToken=default);/// <summary>Remote endpoint to which the connection is connected.</summary>publicIPEndPointRemoteEndPoint{get;}/// <summary>Local endpoint to which the connection is bound.</summary>publicIPEndPointLocalEndPoint{get;}/// <summary>Peer's certificate, available only if the peer provided the certificate.</summary>publicX509Certificate2?RemoteCertificate{get;}/// <summary>Final, negotiated ALPN.</summary>publicSslApplicationProtocolNegotiatedApplicationProtocol{get;}/// <summary>/// Create an outbound uni/bidirectional stream./// </summary>publicValueTask<QuicStream>OpenOutboundStreamAsync(QuicStreamTypetype,CancellationTokencancellationToken=default);/// <summary>/// Accept an inbound stream./// </summary>publicValueTask<QuicStream>AcceptInboundStreamAsync(CancellationTokencancellationToken=default);/// <summary>/// Close the connection and terminate any active streams./// </summary>publicValueTaskCloseAsync(longerrorCode,CancellationTokencancellationToken=default);/// <summary>/// Silently closes the connection if not closed with CloseAsync beforehand./// </summary>publicvoidDisposeAsync();}/// <summary>Options for a new connection, the same options are used for incoming and outgoing connections.</summary>publicabstractclassQuicConnectionOptions{/// <summary>Prevent user sub-classing.</summary>internalQuicConnectionOptions(){}/// <summary>Limit on the number of bidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundBidirectionalStreams{get;set;}/// <summary>Limit on the number of unidirectional streams the remote peer connection can create on an open connection.</summary>publicintMaxInboundUnidirectionalStreams{get;set;}/// <summary>Idle timeout for connections, after which the connection will be closed. Zero means using default of the underlying implementation.</summary>publicTimeSpanIdleTimeout{get;set;}=TimeSpan.Zero;/// <summary>Error code used when the stream needs to abort read or write side of the stream internally.</summary>publicrequiredlongDefaultStreamErrorCode{get;set;}}/// <summary>Options for a new connection, only used for outbound connections.</summary>publicsealedclassQuicClientConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the outgoing connection.</summary>publicrequiredSslClientAuthenticationOptionsClientAuthenticationOptions{get;set;}/// <summary>The endpoint to connect to.</summary>publicrequiredEndPointRemoteEndPoint{get;set;}/// <summary>Optional local endpoint from which the connection is to be established.</summary>publicIPEndPoint?LocalEndPoint{get;set;}publicQuicClientConnectionOptions(){MaxInboundBidirectionalStreams=0;MaxInboundUnidirectionalStreams=0;}}/// <summary>Options for a new connection, only used for incoming connections.</summary>publicsealedclassQuicServerConnectionOptions:QuicConnectionOptions{/// <summary>SSL options for the incoming connection</summary>publicrequiredSslServerAuthenticationOptionsServerAuthenticationOptions{get;set;}publicQuicServerConnectionOptions(){MaxInboundBidirectionalStreams=100;MaxInboundUnidirectionalStreams=10;}}

                API Usage

                Client usage:

                varoptions=newQuicClientConnectionOptions(){RemoteEndPoint=newDnsEndPoint("localhost",5001),DefaultStreamErrorCode=(long)Http3ErrorCode.RequestCancelled,ClientAuthenticationOptions=newSslClientAuthenticationOptions(){ApplicationProtocols=newList<SslApplicationProtocol>(){SslApplicationProtocol.Http3},}};awaitusingvarconnection=awaitQuicProvider.CreateConnectionAsync(options,cancellationToken);awaitusingvarstream=awaitconnection.OpenStreamAsync(StreamDirection.Bidirectional,cancellationToken);// Work with stream, open more of them, send and receive data, close them ... https://github.com/dotnet/runtime/issues/69675// Close will terminate all unclosed streams.// If not called, the peer side of the connection will have to wait for idle connection timeout.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

                Server usage:

                // Consider listener from https://github.com/dotnet/runtime/issues/67560:awaitusingvarconnection=awaitlistener.AcceptConnectionAsync(cancellationToken);while(running){// In case the client closes the connection, Accept will throw appropriate exception.awaitusingvarstream=awaitconnection.AcceptStreamAsync(cancellationToken);// Send and receive data... https://github.com/dotnet/runtime/issues/69675// DisposeAsync called by await using.}// Close will terminate all unclosed streams. Note that H/3 uses GO_AWAY to negotiate graceful connection shutdown with the client.awaitconnection.CloseAsync((long)Http3ErrorCode.NoError,cancellationToken);// DisposeAsync called by await using.

                Alternative Designs

                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.

                Metadata

                Metadata

                Assignees

                Labels

                api-approvedAPI was approved in API review, it can be implementedarea-System.Net.QuicblockingMarks issues that we want to fast track in order to unblock other important work

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions