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 server timeout caused by a packet loss at Handshake #66

Closed
wants to merge 9 commits into from

Conversation

junhochoi
Copy link
Contributor

@junhochoi junhochoi commented Jun 21, 2019

Update stream.send.max_data when handshake is complete.

  • add new get_max_data() for accessing send.max_data
  • change update_max_data()

@junhochoi
Copy link
Contributor Author

#65

@junhochoi junhochoi changed the title #65 Fix timeout and ack to ack Fix timeout at Handshake packet loss Jun 21, 2019
@junhochoi junhochoi changed the title Fix timeout at Handshake packet loss Fix server timeout caused by a packet loss at Handshake Jun 21, 2019
@LPardue
Copy link
Contributor

LPardue commented Jun 24, 2019

BTW this build failure is due to a change in rustfmt. Not that it matter too much, but we can squelch it with a a 1-liner 591feb4

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/stream.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
LPardue
LPardue previously approved these changes Jun 25, 2019
Copy link
Contributor

@LPardue LPardue left a comment

Choose a reason for hiding this comment

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

Looks good now, thanks! It would be nice if we could add quiche unit test for this but I appreciate that it is tricky. One minor comment left about tweaking logging.

Copy link
Member

@ghedo ghedo left a comment

Choose a reason for hiding this comment

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

The fundamental problem here is that we process STREAM frames as well as allow stream writes (including creating stream state) before the handshake is complete, which is probably wrong, so maybe we should just prevent that instead?

In any case we need a test for this case, can you look into adding one @junhochoi ?

src/lib.rs Outdated Show resolved Hide resolved
src/stream.rs Outdated Show resolved Hide resolved
src/lib.rs Show resolved Hide resolved
@ghedo
Copy link
Member

ghedo commented Jun 25, 2019

Also, I fixed the rustfmt config in master, so please rebase to resolve the build failures.

@LPardue
Copy link
Contributor

LPardue commented Jun 25, 2019

The fundamental problem here is that we process STREAM frames as well as allow stream writes (including creating stream state) before the handshake is complete, which is probably wrong, so maybe we should just prevent that instead?

That was one of my initial thoughts but I figured it was by done on purpose to avoid blocking request processing while waiting for Handshake retransmits.

As I get more familiar with this part of the library, it seems weirdly inconsistent that we would start doing things like request processing, while simultaneously delaying the read of the peer's transport params until handshake_complete.

Junho Choi and others added 4 commits June 25, 2019 11:19
When there is a Handshake packet loss, depending on the state
server or client still can proceed and send new data.

e.g.

Client: send Initial #0
Server: got Initial, send ACK, send Initial #0
Client: send Handshake #0, #1
Server: got Hanshake #0, #1, send ACK, send Handshake #0
Client: got Handshake #0, send ACK [#2] #1 [#1 lost], send Application data (h3 request) #0
  (Application data is tranferred in Stream ID 0)
Server: got Application #0

At this point, Handshake is not complete, but both parties received
H3 request and Server writes response header and body to Stream send
buffer. However when Stream ID 0 is created at Server, Handshake is not
complete yet, so it's created with stream.send.max_data = 0 because
default value of initial_max_stream_data_bidi_local is 0 until we get
transport parameter.

This leads response data (header and body) is queued but not sent
because of max_data is not updated for opened stream.

To solve this issue, when handshake is complete, scan opened stream
and update send.max_data by calling send.update_max_data().
Co-Authored-By: Lucas Pardue <lucaspardue.24.7@gmail.com>
When calling stream.send.update_max_data(), default value
need to be what we use when Stream.SendBuf is created.
junho.choi@gmail.com added 2 commits June 25, 2019 11:33
In this case s.send.get_max_data() is always 0 because streams opened
at this point have default values (0), not from a transport
parameter.
@junhochoi
Copy link
Contributor Author

The fundamental problem here is that we process STREAM frames as well as allow stream writes (including creating stream state) before the handshake is complete, which is probably wrong, so maybe we should just prevent that instead?

That was one of my initial thoughts but I figured it was by done on purpose to avoid blocking request processing while waiting for Handshake retransmits.

As I get more familiar with this part of the library, it seems weirdly inconsistent that we would start doing things like request processing, while simultaneously delaying the read of the peer's transport params until handshake_complete.

While looking at it I thought the similar thing but for now I think it is ok/right to delay stream write until handshake is done. On the other hand we may reject stream creation until handshake is complete but it may mean a packet loss from peer side, causing retransmit.

junho.choi@gmail.com added 2 commits June 25, 2019 18:10
Test will try to make a ACK from client at the end of Handshake lost,
check if stream#0.send.get_max_data() == 0, send pending ACK again,
and check if stream#0.send.get_max_data() > 0 to simulate the case.

testing::recv_send() is split into recv() and send()
@ghedo
Copy link
Member

ghedo commented Jun 26, 2019

@LPardue yes, this was originally intended to allow support for receiving 0-RTT data though it hasn't been implemented yet. But note that applications can already enforce not reading data (e.g. process HTTP requests) before connection is established (and indeed we do in our applications), but the problem is that STREAM frames are still processed by the library, so stream state is initialized anyway.

@junhochoi I'm thinking maybe we could still process (decrypt and parse) a limited number of short header ("Application") packets before handshake is complete, but instead of processing the frames immediately they could be buffered and processed when the handshake is completed. This way we wouldn't need to drop packets, unless we receive more than a certain amount (5? 10? To avoid potential DoS).

@ghedo
Copy link
Member

ghedo commented Jun 26, 2019

I made 188588c which should make it easier to implement the frame buffering I suggested in my previous comment, if someone wants to try.

@junhochoi
Copy link
Contributor Author

@junhochoi I'm thinking maybe we could still process (decrypt and parse) a limited number of short header ("Application") packets before handshake is complete, but instead of processing the frames immediately they could be buffered and processed when the handshake is completed. This way we wouldn't need to drop packets, unless we receive more than a certain amount (5? 10? To avoid potential DoS).

Sounds like a good idea. e.g. client is doing POST with a big file. up to initcwnd (10?) will be acceptable. I think QUIC transport draft has something similar.

@ghedo
Copy link
Member

ghedo commented Jun 30, 2019

I looked into implementing the frame buffering thing and made #68. Note that this still allows applications to send data before the handshake is completed, but that can be implemnted separately (and shouldn't be a problem right now for the HTTP/3 use-case).

Basically porting #68 changes into this branch.
Add early_app_pkts to count the number of packet before
Handshake complete.
@junhochoi
Copy link
Contributor Author

junhochoi commented Jul 1, 2019

I looked into implementing the frame buffering thing and made #68. Note that this still allows applications to send data before the handshake is completed, but that can be implemnted separately (and shouldn't be a problem right now for the HTTP/3 use-case).

Thanks. I think the major difference here is whether to allow Application read the packets before Handshake completes -- #66 branch allows and #68 branch doesn't allow (instead it buffers packets). Good thing of #66 is during recovering lost Handshake packet server can read request stream, while #68 waits until Handshake completes and let Application read packets afterwards. (so #66 can be a little faster when origin latency is high in this case but not impacing when cached or directly served by this server). Another aspect is whether to allow Application read packets (stream data) before Handshake fully completes can be a security concern. I think this can be acceptable because until Handshake fully completes server cannot send response back (write is blocked) and if nothing recovered this connection will be closed by timed out eventually.

To limit reading packets while Handshake, I added b008b0f from your #68 branch, basically without buffering, so now server accepts no more than initcwnd packets before Handshake complete.

PTAL @ghedo. Probably we need to pick one.

@junhochoi
Copy link
Contributor Author

#68 provides more compliant solution. Closing this one.

@junhochoi junhochoi closed this Jul 2, 2019
@ghedo ghedo deleted the 269.fix_timeout branch July 18, 2019 15:56
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.

3 participants