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

replace version identifier and require user action #49

Closed
wants to merge 1 commit into from

Conversation

Kriechi
Copy link
Member

@Kriechi Kriechi commented Aug 19, 2017

No description provided.

@Kriechi Kriechi mentioned this pull request Aug 19, 2017
@Lukasa
Copy link
Member

Lukasa commented Aug 19, 2017

@jeamland So I think this looks reasonable but I want you to cast your eye over it when you get a chance.

@@ -378,8 +378,10 @@ def _process_connection_request(self, event):
if b'sec-websocket-version' not in headers:
return ConnectionFailed(CloseReason.PROTOCOL_ERROR,
"Missing Sec-WebSocket-Version header")
# XX FIXME: need to check Sec-Websocket-Version, and respond with a
# 400 if it's not what we expect
versions = _split_comma_header(headers[b'sec-websocket-version'])
Copy link
Member

Choose a reason for hiding this comment

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

According to the spec, the client's sec-websocket-version header is not comma-delimited. It just has to be the literal string b"13", nothing else. So instead of using _split_comma_header, this should just check that headers[b'sec-websocket-version'] == b'13'.

You can see this from the ABNF:

      Sec-WebSocket-Version-Client = version
      version = DIGIT | (NZDIGIT DIGIT) |
                ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)
                ; Limited to 0-255 range, with no leading zeros

And it's also in the text: "The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13." (section 4.1). Also, "the |Sec-WebSocket-Version| header field MUST NOT appear more than once in an HTTP request." (section 11.3.5).

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure which RFC you are reading, but I am using this section:
https://tools.ietf.org/html/rfc6455#section-4.4

which contains these examples:

 The response from the server might look as follows:

      HTTP/1.1 400 Bad Request
      ...
      Sec-WebSocket-Version: 13, 8, 7

   Note that the last response from the server might also look like:

      HTTP/1.1 400 Bad Request
      ...
      Sec-WebSocket-Version: 13
      Sec-WebSocket-Version: 8, 7

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh wait - you are talking about the client request of the handshake.
Sorry, I was referring to the server's response!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it's confusing -- they use Sec-WebSocket-Version for two almost, but not quite, entirely different things. The client always sends it, and it has to send exactly the string b"13" (for the final RFC 6455 version of the protocol). And, if the server doesn't like the version the client sent, then the server has to send back a comma-separated list of versions that it does like.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right - I will change this block to only check if the header value is == b'13'.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, I totally forgot to push my changes - now this should be fixed.

versions = _split_comma_header(headers[b'sec-websocket-version'])
if WEBSOCKET_VERSION not in versions:
return ConnectionFailed(CloseReason.PROTOCOL_ERROR,
"Unsupported Sec-WebSocket-Version")
Copy link
Member

Choose a reason for hiding this comment

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

So... this is a little tricky. According to the spec (section 4.2.2), servers that get an unrecognized version MUST respond in a particular way:

          The |Sec-WebSocket-Version| header field in the client's
          handshake includes the version of the WebSocket Protocol with
          which the client is attempting to communicate.  If this
          version does not match a version understood by the server, the
          server MUST abort the WebSocket handshake described in this
          section and instead send an appropriate HTTP error code (such
          as 426 Upgrade Required) and a |Sec-WebSocket-Version| header
          field indicating the version(s) the server is capable of
          understanding.

Right now we don't really have any way to handle this, right? When a connection fails, we just bail out, and we expect the user to somehow construct an appropriate HTTP response? This seems a bit unfriendly in general, and even more so in this case where we're not even giving the user enough information to construct that response correctly... maybe we should be generating the response ourselves? @jeamland, @Lukasa would appreciate thoughts here.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right - this change simply dumps the problem onto the user to take care off.
The problem for me (in the context of @mitmproxy) is that we want to craft our own response, so wsproto should not respond directly.

I agree it is not perfect, however, still better than simply ignoring the version identifier at all.
Not sure about which version are actually out there, but I am not aware of anything other than version 13...?

Copy link
Member

Choose a reason for hiding this comment

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

There are other versions that represent draft versions of the spec, before it was finalized and became an RFC. Some libraries still support them. In the future there could in theory be more versions. But 13 is currently the only standard version, so that's all wsproto supports.

I'm not sure why you want to craft your own response, given that the spec kinda only gives you one option :-). But also, if you want to hack some odd behavior in here, you do always have the option of ignoring the bytes that wsproto tells you to send, and replacing them with your own instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

mitmproxy is a man-in-the-middle web proxy for HTTP 1 & 2, WebSockets, raw TCP,...
so there all sorts of reasons why our users might want to do things 😄
We also want to to do the handshake ourself, because it is part of our HTTP parsing pipeline already.

Everything else in _process_connection_request raises an exception, so I think it is reasonable to do the same for unsupported versions.

How about we introduce a class UnsupportedVersion(ConnectionFailed) exception, and provide a def generate_unsupported_version() function to return a response with the appropriate data?

Copy link
Member

Choose a reason for hiding this comment

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

If you're doing the handshake yourself, then why do you care how wsproto handles the handshake? :-)

How about we introduce a class UnsupportedVersion(ConnectionFailed) exception, and provide a def generate_unsupported_version() function to return a response with the appropriate data?

This seems like a lot of boilerplate for users to worry about...

Copy link
Member Author

Choose a reason for hiding this comment

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

So, to summarize and finally get a consensus on this:

You don't like throwing an exception if the version doesn't match what wsproto supports.
wsproto should directly respond with the HTTP response message to indicate this error.

This is what I currently plan to use in mitmproxy:
https://github.com/mitmproxy/mitmproxy/blob/c9b8b2e3a3a3fef18dfa66bcd9288f12bef357b6/mitmproxy/proxy/protocol/websocket.py#L51-L75

In L67 https://github.com/mitmproxy/mitmproxy/blob/c9b8b2e3a3a3fef18dfa66bcd9288f12bef357b6/mitmproxy/proxy/protocol/websocket.py#L67 I need some way of identifying the error case (version mismatch). If wsproto only tells me this via an HTTP response, I have to rely on parsing the output of bytes_to_send to check this...

Do you feel more comfortable with a new connection state: ConnectionState.VersionMismatch to indicate this error?

Copy link
Member Author

Choose a reason for hiding this comment

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

@njsmith any update on this?

@@ -380,8 +380,9 @@ def _process_connection_request(self, event):
if b'sec-websocket-version' not in headers:
return ConnectionFailed(CloseReason.PROTOCOL_ERROR,
"Missing Sec-WebSocket-Version header")
# XX FIXME: need to check Sec-Websocket-Version, and respond with a
# 400 if it's not what we expect
if headers[b'sec-websocket-version'] != WEBSOCKET_VERSION:
Copy link
Member

Choose a reason for hiding this comment

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

@Kriechi I think the RFC allows for a client to send multiple allowed versions, with the server allowed to respond with the one it wants to use (or 400 with a set of possible versions). I think therefore this is too strict, in this form.

@Kriechi
Copy link
Member Author

Kriechi commented Jan 6, 2019

@pgjones anything salvageable from this PR - or just close & delete?

@pgjones
Copy link
Member

pgjones commented Jan 6, 2019

I think I got it all in #96.

@pgjones pgjones closed this Jan 6, 2019
@Kriechi Kriechi deleted the version-identifier branch January 6, 2019 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants