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

invalidate sent data when 0-RTT is rejected #2067

Closed
marten-seemann opened this issue Aug 15, 2019 · 1 comment · Fixed by #3066
Closed

invalidate sent data when 0-RTT is rejected #2067

marten-seemann opened this issue Aug 15, 2019 · 1 comment · Fixed by #3066

Comments

@marten-seemann
Copy link
Member

marten-seemann commented Aug 15, 2019

Background

When 0-RTT is rejected, this invalidates all application data sent in 0-RTT packets.

Section 4.2.10 of RFC 8446 says:

If the server rejects the "early_data" extension, the client application MAY opt to retransmit the Application Data previously sent in early data once the handshake has been completed. Note that automatic retransmission of early data could result in incorrect assumptions regarding the status of the connection. For instance, when the negotiated connection selects a different ALPN protocol from what was used for the early data, an application might need to construct different messages. Similarly, if early data assumes anything about the connection state, it might be sent in error after the handshake completes.

For example, it might be the case that the original connection used application protocol X, but the resumed connection negotiates the use of application protocol Y. 0-RTT data is sent under the assumption that protocol X is used. Obviously, one can't just retransmit that data and assume that it's valid for Y.

In QUIC, we also check if the transport parameters used on the old connection are valid. For example, on the original connection the server might have allowed 10 concurrent streams, but that limit was reduced in the mean time and now only allows 5 concurrent streams. A blind retransmission of 0-RTT data might lead to a protocol violation if the client opened more than 5 streams to send 0-RTT data.

Current Implementation

When dialing a 0-RTT-enabled connection (using DialEarly), quic-go returns an EarlySession:
https://github.com/lucas-clemente/quic-go/blob/9b627ac93d5e74268118d6c099265de6eb9dee44/interface.go#L199-L210

This is just a normal Session with an additional method that allows applications to determine when the handshake completes. When 0-RTT is rejected, we currently just retransmit this data in 1-RTT packets, ignoring the problems described in the previous section.

Proposal

When 0-RTT is rejected, we return a quic.Err0RTTRejected from all calls on the session and on streams. An application that wishes to use 0-RTT needs to handle this error gracefully.

We extend the EarlySession interface:

type EarlySession interface {
    Session
    HandshakeCompleted() context.Context
    // If 0-RTT is rejected, the old session becomes invalid.
    // All calls to OpenStream and AcceptStream will return an Err0RTTRejected,
    // as will all calls to Read and Write on streams opened on that session.
    // In that case, NextSession has to be called to obtain the new session.
    // It is invalid to call this method before a Err0RTTRejected has been returned.
    NextSession() Session
}

As soon as this error occurs, the application needs to obtain a new session using NextSession(). This returns the session established during the handshake. It's the application's responsibility to reopen streams and retransmit data (if it wishes to do so) on this new session.

Internally, we won't allocate a new session, but we'll use the call to NextSession to reset application state:

  • all flow-control state is reset to its initial state
  • the streams map to its initial state, thereby invalidating all streams that might have been opened during 0-RTT
  • we remove all 0-RTT packets from the packet history and from the bytes in flight

We don't reset any congestion controller state. Having an RTT estimate is valuable and completely independent of the outcome of the cryptographic handshake.

@marten-seemann
Copy link
Member Author

After offline discussion with @mvdan: The API is probably the best we can do in the general case.

However, most application don't have a need for the most general case: Most of them will just use a single ALPN, and won't be interested in changing any data they sent during 0-RTT in the corner case where 0-RTT is rejected.
This situation calls for a config option quic.Config.RecreateApplicationStateOn0RTTRejection that applications can set if they just want to do the easy thing: Fire some application data in 0-RTT, and just "retransmit" in 1-RTT when 0-RTT is rejected.

We can't use QUIC's retransmission logic for this, since flow control limits might have changed, as well as the stream limit. Instead, we can build a wrapper around the session and the streams, and buffer all writes to streams until the handshake is complete, as suggested in option 2 in libp2p/go-libp2p#1533.

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

Successfully merging a pull request may close this issue.

1 participant