Skip to content
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

Adding WebTransport Handshake to Kestrel #41877

Merged

Conversation

Daniel-Genkin-MS-2
Copy link
Contributor

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 commented May 26, 2022

This is step one in my implementation of WebTransport support in Kestrel! This PR focuses on the handshake over HTTP/3. The next one will add the Middleware layer changes and expose an API for interacting with it. Eventually, I will also implement the handshake for HTTP/2.

HTTP/3 Spec: https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html

What's changed

  • There are 2 new HttpSettingType values (EnableWebTransport and H3Datagram) which are introduced in the spec.
  • New HeaderNames.Protocol which encodes the :protocol pseudo-header
  • New KestrelServerOptions properties (EnableWebTransport and EnableH3Datagram)
    • The getters and setters are new public API
  • Some control logic that prevents malformed connection requests. (the rest will go into the middleware layer)
  • Updated the Http3Sample to support WebTransport over HTTP/3 <--- Should I revert this before merging?

Step 1 of 3 to address: #39583

@Daniel-Genkin-MS-2
Copy link
Contributor Author

Should I keep the changes to the Http3Sample app or should I revert them prior to merging? It is currently the only configured way to use this handshake.

@Daniel-Genkin-MS-2
Copy link
Contributor Author

Unit tests are currently broken. I am going to address Chris' comments then fix the tests tomorrow

Copy link
Member

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit.

src/Servers/Kestrel/samples/Http3SampleApp/Program.cs Outdated Show resolved Hide resolved
Co-authored-by: Günther Foidl <gue@korporal.at>
@JamesNK
Copy link
Member

JamesNK commented May 29, 2022

  • There are 2 new HttpSettingType values (EnableWebTransport and H3Datagram) which are introduced in the spec.

What are people's thoughts on these settings being hardcoded into the HTTP/3 layer? It feels like there should be an extension point to receive and send custom settings. I don't think anything needs to change right now as this is private and can be refactored in the future, but maybe in the future?

@Tratcher
Copy link
Member

  • There are 2 new HttpSettingType values (EnableWebTransport and H3Datagram) which are introduced in the spec.

What are people's thoughts on these settings being hardcoded into the HTTP/3 layer? It feels like there should be an extension point to receive and send custom settings. I don't think anything needs to change right now as this is private and can be refactored in the future, but maybe in the future?

These type of settings seem like they would be difficult to provide via extensions because they indicate that the server has additional HTTP/3 capabilities, so you'd also have to be able to provide that functionality. EnableWebTransport redefines how the server handles CONNECT requests, that's not something that can be added at the application layer.

[InlineData(
((long)Http3ErrorCode.ProtocolError),
nameof(HeaderNames.Method), "CONNECT",
nameof(HeaderNames.Protocol), "NOT_webtransport",
Copy link
Member

@halter73 halter73 May 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still no logic to reject a "NOT_webtransport" protocol. This test is only passing because the request is getting rejected due to a missing path.

We should use a unique Http3StreamErrorException.Message for each rejection reason and we should always be always using the matchExpectedErrorMessage to verify were rejecting the request for the right reasons.

internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, Action<string> matchExpectedErrorMessage = null, string expectedErrorMessage = null)
{
var result = await ReadApplicationInputAsync();
if (!result.IsCompleted)
{
throw new InvalidOperationException("Stream not ended.");
}
if ((Http3ErrorCode)Error != protocolError)
{
throw new InvalidOperationException($"Expected error code {protocolError}, got {(Http3ErrorCode)Error}.");
}
matchExpectedErrorMessage?.Invoke(expectedErrorMessage);
}

I don't think matchExpectedErrorMessage?.Invoke(expectedErrorMessage) is never going to fail though. 🤣 It looks like we need to update WaitForStreamErrorAsync to check matchExpectedErrorMessage against the abortReason.Message passed into TestStreamContext.Abort(ConnectionAbortedException abortReason) while we're at it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe it can look at the LogMessages like Http2TestBase does.

if (expectedErrorMessage != null)
{
Assert.Contains(LogMessages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, ok thank you for clarifying. I didn't understand what you meant before. I will make sure to fix this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this logic should be in place, and I originally had it here. However, Chris told me, in an earlier comment, that he wants to keep this chain of if statements completely general and that the middleware should do the checking for webtransport specific conditions. So, I removed it from here.

Copy link
Member

@halter73 halter73 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, Chris told me, in an earlier comment, that he wants to keep this chain of if statements completely general and that the middleware should do the checking for webtransport specific conditions. So, I removed it from here.

I talked this over with Chris and I agree. Other protocols could use the "extended CONNECT method" defined in https://datatracker.ietf.org/doc/html/rfc8441#section-4, so it should be the middleware verifying the protocol is "webtransport".

I still think we should be verifying the error messages in this test. Sorry that this turned into a little bit of yak shaving fixing the WaitForStreamErrorAsync, but I can help out with that.

nameof(HeaderNames.Origin), "server.example.com")] // no authority
public async Task WebTransportHandshake_IncorrectHeadersRejects(long error, params string[] headers)
{
_serviceContext.ServerOptions.AllowAlternateSchemes = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_serviceContext.ServerOptions.AllowAlternateSchemes = true;

Since the extended connect method spec doesn't require "https" scheme, let's go back to using the actual "http" scheme in these tests.

Copy link
Contributor Author

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't http3 require https though? So, for consistency with the real use cases, shouldn't we try to maximize the use of https?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this offline. My feeling is unless we're doing something special with the scheme value in this particular test, it should match the other tests as much as possible.

throw new Http3StreamErrorException(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader, Http3ErrorCode.ProtocolError);
}

if (!_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Authority) || !_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Path))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reading of the "extended CONNECT method" spec is that :scheme and :path are required, but not :authority.

The spec is basically saying that we should treat :authority the way we normally would for non-CONNECT requests, which we're doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. So, since webtransport requires :authority, this check should be in the Middleware?

@@ -189,6 +189,9 @@ public static class HeaderNames
/// <summary>Gets the <c>Pragma</c> HTTP header name.</summary>
public static readonly string Pragma = "Pragma";

/// <summary>Gets the <c>Protocol</c> HTTP header name.</summary>
public static readonly string Protocol = ":protocol";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you submit an API proposal for this? I know there are going to be a bunch more APIs, but it might be good to learn the process by quickly filling out the issue template.

Copy link
Member

@Tratcher Tratcher Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already did as part of my h2 WebSockets PR. This field is duplicated in both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an API proposal for that yet? The PR doesn't count! I figured approving this one API would be easier than approving the full extended connect server feature we were discussing.

Copy link
Contributor Author

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tratcher have you already filed one for :protocol as part of your websockets PR?
Edit: nevermind, should have refreshed the page sooner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API proposal: #42002

Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should wait for #42002 to be api-approved before merging. If this is blocking anything, I expect we can approve it very quickly over email considering it's a small API change.

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 enabled auto-merge (squash) June 2, 2022 21:44
@Daniel-Genkin-MS-2
Copy link
Contributor Author

Daniel-Genkin-MS-2 commented Jun 2, 2022

You can set this in the csproj https://docs.microsoft.com/en-us/dotnet/core/extensions/httpclient-http3#httpclient-settings

Ok Cool! I will do this in the dedicated sample app for webtrasport. This one is shared for all http/3 so I think it is best to keep it siloed
Nevermind. Upon offline discussion, I will include this in the csproj lol

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 merged commit d96a100 into dotnet:main Jun 3, 2022
@ghost ghost added this to the 7.0-preview6 milestone Jun 3, 2022
@Daniel-Genkin-MS-2
Copy link
Contributor Author

Oh, I didn't realize that auto-merge was on. I think I must have clicked it accidentally. I still intend to go through the API review process on Monday with this change.

@ghost
Copy link

ghost commented Jun 3, 2022

Hi @Daniel-Genkin-MS-2. It looks like you just commented on a closed PR. The team will most probably miss it. If you'd like to bring something important up to their attention, consider filing a new issue and add enough details to build context.


/// <summary>
/// SETTINGS_ENABLE_WEBTRANSPORT, default is 0 (off)
/// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html#name-http-3-settings-parameter-r

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Daniel-Genkin-MS-2 , just curious because I had one kesterl WebTransport implementation month ago which add this according to the draft:

// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-h3-websockets-02#section-5
ENABLE_CONNECT_PROTOCOL = 0x8

Does it mean we actually don't have to set enable connect protocol manually and can get WebTransport worked?
Btw thanks for your great work :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1someone1 your link is for WebSockets, it's a different spec. For WebTransport it says this:

[RFC8441] defines an extended CONNECT method in Section 4, enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. That parameter is only defined for HTTP/2. This document does not create a new multi-purpose parameter to indicate support for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT.

From: https://ietf-wg-webtrans.github.io/draft-ietf-webtrans-http3/draft-ietf-webtrans-http3.html#name-extended-connect-in-http-3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That make sense, thank you!

@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants