dotnet / dotnet/aspnetcore

Negotiate H2C on a Kestrel endpoint that supports HTTP/1.1 and HTTP/2

Open
#56,984 6 comments 6 reactions 0 assignees View on GitHub
area-networking feature-kestrel
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

Kestrel endpoints support multiple HTTP versions side-by-side. This feature requires the endpoint use TLS. The ALPN identifier in the TLS handshake tells Kestrel which HTTP version to use.

If an endpoint is cleartext (doesn't have TLS) then the connection always falls back to HTTP/1.1. If a client sends a prior knowledge H2C request to the server, the server will error with HTTP_1_1_REQUIRED.

This change in behavior can be confusing to users. They might use TLS in their local development environment, and then switch to cleartext in production. They don't know the mechanics of protocol negotiation and won't expect their app to break.

An example of a user hitting this problem: https://github.com/dotnet/dotnet-docker/issues/5691

Working around the lack of negotiation over cleartext can require significant changes to an app. Multiple ports must be exposed, one supporting HTTP/1.1 (required for browsers who don't support HTTP/2 over cleartext) and another support HTTP/2 only.

### Describe the solution you'd like

Kestrel to support negotiation between HTTP/1.1 and HTTP/2 without TLS. An HTTP/2 connection always starts with a [connection preface](https://www.rfc-editor.org/rfc/rfc7540#section-3.5).

Steps:

* Inspect the first bytes send over a connection for HTTP/2 connection preface
* Extra logic only runs when endpoint is cleartext and includes HTTP/1.1 and HTTP/2 as supported protocols. It doesn't matter if HTTP/3 is included or not. There is no special negotiation for HTTP/3 (it requires TLS and so can't work), but it doesn't stop negotiation between HTTP/1.1 and HTTP/2
* After the first bytes are inspected and the protocol is decided, the connection remains that protocol until the connection ends. A connection doesn't support both protocol's requests and doesn't support changing protocol later.

Should be mindful of [cross-protocol attacks](https://www.rfc-editor.org/rfc/rfc7540#section-10.2) and properly mitigate.

### Additional context

golang's h2c support includes protocol negotation over cleartext. See https://pkg.go.dev/golang.org/x/net/http2/h2c#NewHandler

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.