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

Fix auto-upgrade from SPDY/3 to SPDY/3.1 #31

Closed
wants to merge 1 commit into from

Conversation

anandsuresh
Copy link
Contributor

The current code auto-upgrades all spdy/3 clients to spdy/3.1 when
operating in plain mode with autoSpdy31 set to true. This seems
rather restrictive, implying that the server does not support SPDY/3-only
clients with autoSpdy31 set to true, .

This commit fixes this by only upgrading spdy/3 clients to spdy/3.1 when
the server receives a WINDOW_UPDATE for stream id 0. This ensures
that spdy/3 clients can proceed uninhibited when operating in plain
mode, and ensuring that the autoSpdy31 option is only used for
spdy/3.1 clients in plain mode, allowing for both spdy/3 and
spdy/3.1 clients to co-exist on the same server.

@@ -281,7 +281,7 @@ Connection.prototype._handleFrame = function _handleFrame(frame) {

// Session window update
if (frame.type === 'WINDOW_UPDATE' && frame.id === 0) {
if (state.version < 3.1 && state.autoSpdy31) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe the logic here was: if we receive id=0 WINDOW_UPDATE and we are on a version 3 (or less, yeah it should be === 3 here) and we want to auto-upgrade - do it.

Why do you suggest that autoSpdy31 should be removed from this if?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The case I'm working with is in plain mode.

If autoSpdy31 is set to false, then the only way for the SPDY server to detect a SPDY/3.1 client is the id=0 WINDOW_UPDATE. However, since autoSpdy31 is set to false, the highlighted line above causes the SPDY/3.1 client to be treated as a SPDY/3 client, leading to issues when the amount of data transferred exceeds the INITIAL_WINDOW_SIZE for the connection. With no session-level flow control in SPDY/3, the SPDY/3.1 client stops transmitting after the INITIAL_WINDOW_SIZE bytes have been sent and no recovery is possible because SPDY/3 doesn't deal with id=0 WINDOW_UPDATE frames.

On the other hand, when we set autoSpdy31 to true, the SPDY server sends an id=0 WINDOW_UPDATE (https://github.com/indutny/spdy-transport/blob/master/lib/spdy-transport/connection.js#L246-L247) when a new client connects (due to the second half of the OR expression). This works fine for SPDY/3.1 clients, but with a SPDY/3 client, the id=0 WINDOW_UPDATE results in a GOAWAY frame from the client since SPDY/3 doesn't deal with session-level flow control (iSPDY does this because it fails to locate a stream with id=0 and decides to drop the connection: https://github.com/Voxer/iSPDY/blob/master/src/ispdy.m#L1227-L1242).

Thus, it seems that the autoSpdy31 flag serves as a big hammer that treats all clients as SPDY/3.1 clients when the flag is set, and as SPDY/3 clients when the flag is unset.

What we're looking for is the ability to handle both SPDY/3 and SPDY/3.1 clients in plain mode.

Removing the autoSpdy31 flag check in the line highlighted above produces the desired result, gracefully upgrading SPDY/3.1 clients, while allowing SPDY/3 clients to proceed unmodified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@indutny Does the above explanation work?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry for delay! Not really.

If autoSpdy31 is false - the update from 3 to 3.1 should not happen at all. That's why we have this option in place. Actually, this option makes sense only with plain: true, because otherwise protocol version is negotiated via NPN.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a way to get both SPDY/3 and SPDY/3.1 clients to work in plain mode? In its current form, the autoSpdy31 flag causes the library to be purely binary with respect to v3 and v3.1. Its all or nothing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

So autoSpdy31: true, plain: true doesn't work now? What happens?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So in the current case, with autoSpdy31: true, plain: true, SPDY/3.1 clients work fine. But with a SPDY/3 client, the server sends a id=0 WINDOW_UPDATE because of https://github.com/indutny/spdy-transport/blob/master/lib/spdy-transport/connection.js#L246-L247 and https://github.com/indutny/spdy-transport/blob/master/lib/spdy-transport/connection.js#L475. Because SPDY/3 doesn't implement session-level flow-control, a SPDY/3 client may treat that frame as a protocol error/violation and drop the connection (as iSPDY does because it fails to locate a stream with id=0 and decides to drop the connection: https://github.com/Voxer/iSPDY/blob/master/src/ispdy.m#L1227-L1242).

I've pushed a fix for this issue, by adding && state.version >= 3.1 to the checks. This ensures that the id=0 WINDOW_UPDATE is only sent for SPDY/3.1 clients when autoSpdy31 is set to true.

Without this additional check, the server sends id=0 WINDOW_UPDATE to a SPDY/3 client causing it to disconnect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to be clear, I made additional changes that partially revert my previous change (I put the && state.autoSpdy31 check back in) and added the && state.version >= 3.1 condition to https://github.com/indutny/spdy-transport/blob/master/lib/spdy-transport/connection.js#L246 and https://github.com/indutny/spdy-transport/blob/master/lib/spdy-transport/connection.js#L475.

So now, the server doesn't send a id=0 WINDOW_UPDATE as soon as the client connects. Instead it waits for the client to send up a id=0 WINDOW_UPDATE, upgrades the connection to SPDY/3.1 and then sends the id=0 WINDOW_UPDATE.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thank you! This sounds very reasonable to me!

I'll review it tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sweet!

The current code auto-upgrades all SPDY/3 clients to SPDY/3.1 when
operating in **plain** mode with **autoSpdy31** set to true. This seems
rather restrictive, implying that with autoSpdy31 set to true, the
server does not support SPDY/3-only clients.

This commit fixes this by only upgrading SPDY/3 clients to SPDY/3.1 when
the server receives a WINDOW_UPDATE for stream id 0. This ensures
that SPDY/3 clients can proceed uninhibited when operating in **plain**
mode, and ensuring that the autoSpdy31 option is only used for
SPDY/3.1 clients in **plain** mode, allowing for both SPDY/3 and
SPDY/3.1 clients to co-exist on the same server in **plain** mode.
@anandsuresh
Copy link
Contributor Author

@indutny Are there any more changes needed on this one? If not, can this be merged and released? Thanks.

@anandsuresh
Copy link
Contributor Author

@indutny Can we merge this if you're satisfied with the review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants