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

Detect HTTP/1.1 on HTTP/2 connection #39358

Merged
merged 12 commits into from
Jan 12, 2022
Merged

Conversation

JamesNK
Copy link
Member

@JamesNK JamesNK commented Jan 6, 2022

Addresses #14884

image

What do we think? It's not perfect, but it should be a lot more informative in most cases. And extra work only happens if request is already been rejected so has no perf impact. Will add unit tests if this idea has merit.

Also, we can do something similar for HTTP/1.1 (on invalid request line, check if the content is an HTTP/2 connection preface)

@Tratcher
Copy link
Member

Tratcher commented Jan 6, 2022

Could we accomplish most of this by adding more content to Http2ErrorInvalidPreface without doing the additional checks? How much does it matter which specific version we detected? We could make multiple recommendations in the same error message.

A lot of the people that run into this aren't even looking at the server logs, they only notice a connection abort on the client. If we detected HTTP/1.x would it be possible to send back a hard coded 400 response before aborting the connection?

@TanayParikh
Copy link
Contributor

/azp run

@TanayParikh
Copy link
Contributor

Needs a full re-run given #39361.

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@JamesNK
Copy link
Member Author

JamesNK commented Jan 6, 2022

Is sending back HTTP/1.1 on a HTTP/2 connection be legal? Sure, the client is wrong, but do two wrongs make a right?

Changing the server response is a much bigger piece of work. To do that I think this would need to be completely robust (i.e. when detecting the error, read additional content until either the end of the line or max request line length is reached). Then send a valid HTTP/1.1 response. That's a lot more work.

Agree that returning an error to the client would help more people, but this is a start.

@Tratcher
Copy link
Member

Tratcher commented Jan 6, 2022

Is sending back HTTP/1.1 on a HTTP/2 connection be legal? Sure, the client is wrong, but do two wrongs make a right?

But it's not an HTTP/2 connection, is it? This check says the client is trying to do HTTP/1.x. The server hoped for HTTP/2 but didn't get it. The goal is to respond in a way the client will understand.

@JamesNK
Copy link
Member Author

JamesNK commented Jan 7, 2022

Ok.

I want to understand what is the ideal solution. It is more work than I expected to do so I want to make sure it is done right the first time and I don't spend time doing something that people don't want.

Proposal:

  1. Read connection to prefix length
  2. Check prefix
  3. New: If invalid, instead of throwing connection exception, read connection until /n (or max request line length, or timeout is triggered)
  4. New: Check if line is HTTP/1.x (how comprehensive do you want the check to be? Right now I'm just checking for the trailing "HTTP/1.1" on the request line. Do we care if the method or path is invalid?)
  5. New: If the request is HTTP/1.x then return a 400 response and end the connection
  6. If not HTTP1/x (no newline bytes, or exceeded request line length, or no HTTP/1.x version at end of the line) then throw connection error like it does today.

@Tratcher
Copy link
Member

Tratcher commented Jan 7, 2022

Sounds right.

The other case to consider in this code path is 5. Client (TLS) calling no TLS port, not for the same PR but keep it in mind while working here. If we don't detect HTTP/1.x then we could fall back to sniff for TLS frames (you only need ~5bytes to check for the basic structure). If that's a match then we could respond with a fatal TLS Alert like unexpected_message or illegal_parameter to make the error more searchable than a generic TCP RST. protocol_version is tempting, but that implies TLS protocol.

https://techcommunity.microsoft.com/t5/iis-support-blog/ssl-tls-alert-protocol-and-the-alert-codes/ba-p/377132

@JamesNK
Copy link
Member Author

JamesNK commented Jan 7, 2022

@Tratcher Updated. Needs tests and cache the hardcoded response. Otherwise, is this what you had in mind?

@Tratcher
Copy link
Member

Tratcher commented Jan 7, 2022

Exactly, it gives us a chance to tell the client what they did wrong without them having to go dig through the server logs.

JamesNK and others added 2 commits January 9, 2022 16:16
@JamesNK
Copy link
Member Author

JamesNK commented Jan 9, 2022

Why isn't a connection error thrown from matching the preface if the reader ends before reading up to the preface length? In that case TryReadPrefaceAsync returns false, and the connection sends a GOAWAY of NO_ERROR.

Is that against the spec - https://httpwg.org/specs/rfc7540.html#rfc.section.3.5 - Bug?

I updated TryReadPrefaceAsync to throw Http2ConnectionErrorException with a PROTOCOL_ERROR when ReadResult.IsComplete is true.

@Tratcher
Copy link
Member

Why isn't a connection error thrown from matching the preface if the reader ends before reading up to the preface length? In that case TryReadPrefaceAsync returns false, and the connection sends a GOAWAY of NO_ERROR.

Is that against the spec - https://httpwg.org/specs/rfc7540.html#rfc.section.3.5 - Bug?

I updated TryReadPrefaceAsync to throw Http2ConnectionErrorException with a PROTOCOL_ERROR when ReadResult.IsComplete is true.

If the reader ends then the connection is likely already closed. It's also common for port scanners to connect but not send any data. Neither case requires a response, or an exception log.

@JamesNK
Copy link
Member Author

JamesNK commented Jan 11, 2022

Why isn't a connection error thrown from matching the preface if the reader ends before reading up to the preface length? In that case TryReadPrefaceAsync returns false, and the connection sends a GOAWAY of NO_ERROR.
Is that against the spec - httpwg.org/specs/rfc7540.html#rfc.section.3.5 - Bug?
I updated TryReadPrefaceAsync to throw Http2ConnectionErrorException with a PROTOCOL_ERROR when ReadResult.IsComplete is true.

If the reader ends then the connection is likely already closed. It's also common for port scanners to connect but not send any data. Neither case requires a response, or an exception log.

Ok. FYI today the server returns a GOAWAY frame with NO_ERROR. If you think nothing should be sent then that would be a change. I'm going to leave current behavior as-is and update my tests in this area.

@JamesNK JamesNK changed the title Detect HTTP version on invalid HTTP/2 connection Detect HTTP/1.1 on HTTP/2 connection Jan 11, 2022
@JamesNK JamesNK enabled auto-merge (squash) January 11, 2022 03:55
@Tratcher
Copy link
Member

Have you tried it with HttpClient to make sure it works as expected?

@JamesNK
Copy link
Member Author

JamesNK commented Jan 12, 2022

Yes, but manually. Not in an integration test. I'll see if I can add one.


if (state.HasFlag(ReadPrefaceState.Http1x))
{
if (ParseHttp1x(readableBuffer, out var detectedVersion))
Copy link
Member

@halter73 halter73 Jan 12, 2022

Choose a reason for hiding this comment

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

Nit: It's not super obvious from the name that ParseHttp1x returns true when the MaxRequestLineSize is exceeded before an \r could be found. Maybe ParseHttp1x should just update the ReadPrefaceState or return its own tri-state enum.

@JamesNK JamesNK merged commit 26adec8 into main Jan 12, 2022
@JamesNK JamesNK deleted the jamesnk/kestrel-detect-version branch January 12, 2022 23:10
@ghost ghost added this to the 7.0-preview1 milestone Jan 12, 2022
@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.

5 participants