Data is protected using a number of encryption levels: <del> Plaintext </del>  Early Data (0-RTT) Keys level.  For instance, an implementation might bundle a Handshake message and an ACK for some Handshake data into the same packet. <del> Each encryption level has a specific list of frames which may appear in it.  The rules here generalize those of TLS, in that frames associated with establishing the connection can usually appear at any encryption level, whereas those associated with transferring data can only appear in the 0-RTT and 1-RTT encryption levels: CRYPTO frames MAY appear in packets of any encryption level except 0-RTT. CONNECTION_CLOSE MAY appear in packets of any encryption level other than 0-RTT. </del>  PADDING frames MAY appear in packets of any encryption level.  ACK frames MAY appear in packets of any encryption level other than 0-RTT, but can only acknowledge packets which appeared in that packet number space. <del> STREAM frames MUST ONLY appear in the 0-RTT and 1-RTT levels. </del>  <del> All other frame types MUST only appear at the 1-RTT levels. </del>  Because packets could be reordered on the wire, QUIC uses the packet type to indicate which level a given packet was encrypted under, as The keys used for packet protection are computed from the TLS secrets using the KDF provided by TLS.  In TLS 1.3, the HKDF-Expand-Label <del> function described in Section 7.1 of TLS13) is used, using the hash </del>  function from the negotiated cipher suite.  Other versions of TLS <del> MUST provide a similar function in order to be used QUIC. </del>  The current encryption level secret and the label "quic key" are input to the KDF to produce the AEAD key; the label "quic iv" is used to derive the IV, see aead.  The header protection key uses the "quic <del> hp" label, see header-protect).  Using these labels provides key </del>  separation between QUIC and TLS, see key-diversity. The KDF used for initial secrets is always the HKDF-Expand-Label in hexadecimal notation.  Future versions of QUIC SHOULD generate a new salt value, thus ensuring that the keys are different for each version of QUIC.  This prevents a middlebox that only recognizes one <del> version of QUIC from seeing or modifying the contents of handshake packets from future versions. </del>  The HKDF-Expand-Label function defined in TLS 1.3 MUST be used for Initial packets even where the TLS versions offered do not include All QUIC packets other than Version Negotiation and Retry packets are protected with an AEAD algorithm AEAD.  Prior to establishing a shared secret, packets are protected with AEAD_AES_128_GCM and a key <del> derived from the destination connection ID in the client's first </del>  Initial packet (see initial-secrets).  This provides protection against off-path attackers and robustness against QUIC version unaware middleboxes, but not against on-path attackers. The "extension_data" field of the quic_transport_parameters extension contains a value that is defined by the version of QUIC that is in use.  The quic_transport_parameters extension carries a <del> TransportParameters when the version of QUIC defined in QUIC- </del>  TRANSPORT is used. The quic_transport_parameters extension is carried in the ClientHello 9.1.  A small ClientHello that results in a large block of handshake messages from a server can be used in packet reflection attacks to amplify the traffic generated by an attacker. attacker cannot forge them.  Put together, these defenses limit the level of amplification. <del> 9.2. </del>  QUIC, TLS, and HTTP/2 all contain messages that have legitimate uses in some contexts, but that can be abused to cause a peer to expend implementations SHOULD track redundant packets and treat excessive volumes of any non-productive packets as indicative of an attack. <del> 9.3. </del>  Header protection relies on the packet protection AEAD being a pseudorandom function (PRF), which is not a property that AEAD payloads and packet numbers MUST be free from side-channels that would reveal the packet number or its encoded size. <del> 9.4. </del>  In using TLS, the central key schedule of TLS is used.  As a result of the TLS handshake messages being integrated into the calculation

In , NAME suggests that we could allow RESETSTREAM in 0-RTT. That seems slightly wrong, because why would someone send something then give up without receiving anything in return, but that is actually possible in the presence of packet loss. And changing your mind is perfectly acceptable. But RESETSTREAM doesn't really cover it. Streams can get blocked, requests can go out with additional flow control credits, and maybe even STOPSENDING makes sense. And my analysis suggests that the risk from replay is not made worse from any frame type that doesn't produce application-visible changes. So this removes the restrictions, aside from the natural restrictions that exist for frames (for instance, there are no cases where ACK, CRYPTO, and even PATHRESPONSE make sense to send in 0-RTT).
Thede rules seem to get more and more complex. As I mentioned in the discussion on the issue, I’d prefer to allow all frames in 0-RTT. For example, what’s the harm of sending a PING in 0-RTT?
OK, big changes here based on doing more analysis. Please re-review.
As NAME notes, there is a reasonable argument on the issue that most frame types should simply be permitted. Would it make more sense to invert the list and instead state only where frames are prohibited?
Proposed shortening of replay section As described in Section 8 of {{!TLS13}}, use of TLS early data comes with an exposure to replay attack. The use of 0-RTT in QUIC is similarly vulnerable to replay attack. Endpoints MUST implement and use the replay protections described in {{!TLS13}}, however it is recognized that these protections are imperfect. Therefore, additional consideration of the risk of replay are needed. The QUIC transport protocol is not vulnerable to replay attacks and does tolerate replayed frames because all frames are idempotent and because no state is carried across connections. Applications might carry state across connections and can be confused by STREAM frame content under 0-RTT keys and also by other content such as flow control credits. Servers can be configured to reject old 0-RTT packets but it is not perfect nor guaranteed. Only application protocols can define semantics that are acceptable for 0-RTT sessions, if any. A copied 0-RTT session is cheap to establish and can be exploited to expend excessive server resources. Applications MUST account for this overhead before accepting 0-RTT connections. QUIC extensions SHOULD describe how replay attacks affect their operation. Applications MUST prohibit extensions where efficient replay mitigation strategies cannot be provided.
I am not sure if this is accurate. IIUC their is nothing that mandates the client to switch to using 1-RTT when it receives ServerFinished. In fact, on a lossy network, a client would benefit from using 0-RTT packets until it receives an ACK for ClientFinished. In such case, it would be natural to observe these frames in a 0-RTT packet. I think we should either forbid them or clarify that there are allowed, instead of suggesting (IMO incorrectly) that it’s impossible. Originally posted by NAME in URL
To be clear, this was in reference to text that mentioned that certain frames couldn't be sent in 0-RTT. That would only be true if we made this a requirement.
This actually is already addressed in the TLS draft, as NAME pointed out in another thread: , "Though an endpoint might retain older keys, new data MUST be sent at the highest currently-available encryption level." If you have 1-RTT keys, you MUST use them. There's currently no server verification that you do so, however.
I think this should be stated in transport as well as TLS, along with a statement that a server MAY/SHOULD/MUST ignore 0RTT packets that have higher packet numbers than the first received 1RTT packet. The first is editorial I believe, the latter is borderline design. If we decided it was valid to close the connection if a 0RTT packet was received with a higher packet number than 1RTT, then that's definitely not editorial, but I think it may be a good way to enforce proper behavior.
NAME Agreed. I think that this should be a MAY/SHOULD close the connection with PROTOCOL_VIOLATION rather than "ignore", because that aligns the handling with how we agreed to handle other misuse of 0-RTT packets (see URL).
NAME said it right at the mike: this is a key update scenario, from 0-RTT to 1-RTT key. Treat it the same way as peer continuing using old key after key update.
I think the only verifiable requirement is "must not ack 1RTT packet in 0RTT packet". The server can definitely detect that, and send a connection close with reason PROTOCOL VIOLATION when it happens. This would effectively force the client to switch to 1RTT soon.
NAME exceeding the initial flow control / max streams from the remembered TP's is also detectable.
NAME detection with flow control would require special case code in stream processing, as in "check against the current window but if 0-RTT check against the initial window". Could be done, but that would be another wart. Also, in many cases the 0-RTT data sent is well below the initial flow control window, so it will not be a very efficient test.
NAME It's actually something that can be implemented in a clear and concise way. The only thing you need to have are objects representing limits for each type of packets. When processing 0-RTT packets, the logic would refer to the limits declared by TP of the previous connection. When processing 1-RTT packets, the logic would refer to the limits declared by current TP and the max frames.
Since QUIC provides 0-RTT support, it really should.
forbids a RESETSTREAM frame being sent in a 0-RTT packet. I do not see a reason why we should prohibit that even though we allow FIN to be sent. RESETSTREAM is just another way of closing a stream. I think that we should allow RESETSTERAM to be sent using a 0-RTT packet. FWIW, my assumption is that we simply missed to clarify that RSTSTREAM is allowed when we split the frame into RST and STOP_SENDING.
Do we really need any special rules for allowed frame types in 0-RTT packets? I don't see any justification for treating 0-RTT and 1-RTT different in that regard.
NAME My understanding is that 0-RTT is a temporary state that provides less protection (i.e. not forward-secret and also replay-able). It makes sense to require clients to switch to 1-RTT ASAP. I think that the current approach of limiting the capability to the transmission of stream data is one way of accomplishing that.
If RESETSTREAM is to be permitted, shouldn't it also be permitted to send STOPSENDING on locally initiated bidirectional streams?
I don't understand why we would consider e.g. flow control updates worthy of higher protection than application data. Note that there's a real use case for sending flow control updates in 0-RTT packets: A client might anticipate a large response when it sends a request in 0-RTT request, and it can save one roundtrip by granting a lot of flow control credit for that response.
NAME I'm not sure if that's true. A client can always use transport parameters to specify it's limits; I doubt if there's a benefit in updating the limits using a 0-RTT packet. And in case of anticipating a large response, the anticipation would be that the transmission of the response would span across multiple round-trips (because of congestion control). Everything would work fine if the client's initial limit is large enough to cover what can be sent in the first round-trip (or several round-trips). Asking for permission to send BLOCKED frames in 0-RTT packets might make sense, though I am not sure if we'd need that in practice. I think we can expect a server allowing 0-RTT to provide enough window to let the client actually use 0-RTT.
There's generally good reason to keep the initial window small, since we don't have any mechanism to update the transport parameters. That depends very much on the concrete values used. Right, I hadn't even thought about BLOCKED. I think it proves my point here: If we don't allow those frame types in 0-RTT, we'll need a lot of special code just for keeping those frames out of 0-RTT packets (and to delay transmission until you have 1-RTT keys). Equivalently, on the receiver side, you'd need to implement checks that the payload of 0-RTT packets only contain valid frames. I don't see any reason for special casing anything here, so we should just allow all frame types in 0-RTT packets.
I can imagine an application protocol oriented around using streams as message framing that might have small initial limits to support proportionately large numbers of in-flight streams, but occasionally request large responses by issuing MAXSTREAMDATA alongside the request. Supporting such a pattern in 0-RTT seems useful.
I agree with NAME I think we should just have the same rules for 0-RTT and 1-RTT packets.
Acks cannot be sent in 0rtt packets atm, are you proposing changing that as well NAME
I’m on board with all other frame types though. I guess acks cannot logically be sent in 0rtt naturally, so maybe that’s not really a big deal
I agree that it makes sense to treat 0-RTT and 1-RTT the same since all frames the same. I'd still like to keep the handshake frames to the bare minimum.
I agree with loosening the restrictions. As with TLS 1.3, it's up to the application layer to restrict what things can't be done in 0-RTT. Those should generally be things which have effects outside the connection itself, and at the QUIC layer, nothing the client sends does.
I have been skimming through the QUIC transport and QUIC TLS draft a few times. It is really difficult to decipher which frames can be sent where. Some packet types mention permitted frames in transport, but does not do that for all packet types. QUIC TLS has a list of frames for different packet types, but it is not clear if that cover all frame types. QUIC TLS does suggest that CRYPTO frames can be sent in 1-RTT by stating that only 0-RTT cannot contain crypto, but is not clear about how or why that would happen, although I suspect it has to do with client auth - which is explicitly not permitted post handshake without any explanation. It could also be rekeying, but I think that is derived locally from session key.
This is useful for , for example.
Bring the discussion back from . I'm thinking about NAME comment there and I think that it would be easier if we were able to permit any frame type in 0-RTT (except CRYPTO, which has nothing to carry). In thinking about this, the main concern is whether there is any replay risk associated with any of these frames. Going through each, I didn't find anything. Though replay does result in spurious state being created, it's all transient and connection-bound, not application state. In general, all connection-level control messages affect only connection state, which goes away when the connection goes away. And replayed 0-RTT always results in the connection being thrown out as the full handshake cannot successfully complete twice. There might be some application-level significance to resetting streams in addition to carrying stream data, particularly when you consider the potential for selective replay. An attacker with sufficient knowledge of the contents of packets could replay some packets and not others to effect changes. But while the signals that a server application might receive are different, they are not outside of the range of possible signals that a client accepts as possible when sending the 0-RTT. That is, if the server receives a partial stream rather than a complete stream, that is a potential outcome that the client has to accept as a possibility even when there is no attack. If there are lasting application-layer effects from a RESETSTREAM (or STOPSENDING), then this might cause problems. But the application actively chooses to accept those risks by triggering these actions before the handshake is complete. In addition, RESETSTREAM is a poor carrier for state-changing action because its semantics are better suited to abandoning state-changing effects, not creating them. It would be a poor choice in protocol design to carry any application-layer signal with RESETSTREAM that implied a need for some action. HTTP does this with CONNECT in a way, but that is only to match with a TCP RST and comes with no real expectation of it being reliable. In the end, I conclude that we should permit all frames in 0-RTT and 1-RTT. While there is some increased risk here, I think that this is best left to the 0-RTT profile for the protocol that uses QUIC. For HTTP, where the only application-layer effect RESET_STREAM can have is to cancel requests, the result is to reduce exposure to replay. Other protocols will need to make their own assessment about safety, but I don't anticipate problems. I'll adjust to match.
I think this is an improvement but I am a bit anxious. What happens to special frames related to connection migration that might affect a server view of a remembered client. What about tokens stored for future use - perhaps when looking towards v2 where things get more symmetrical. What about path migration and validation. What about 100 coalesced 0-RTT packets forcing 3x amp path validation, or something, in each - is that a possibility? (EDIT: no because you should protect against repeat 0-RTT, but perhaps it gets more important now) I like the simplicity of all frames - but black listing frames in a 0-RTT receive handler is not hard either - iff valid frames are clearly document. There could be a lot of hidden complexity in 0-RTT that we do not fully appreciate yet.
NAME The problem with 0-RTT in QUIC is that, unlike TLS 1.3 (that has maxearlydatasize), there is no limit in the amount of data that the client can send using the 0-RTT key. IIUC, currently, it's entirely up to the client to decide when it switches from 0-RTT to 1-RTT keys. As we know, Initial and 0-RTT packets can be replayed against different servers within a server cluster that does not (cannot) implement the use-only-once rule for the session tickets. I am concerned that widening how 0-RTT packets can be used might lead to additional exposure to the attack surface. An attack would go like this: an attacker creates a legitimate connection and obtains a session ticket the attacker reconnects using the session ticket, and exchanges lots of information with the server (involving MAXDATA, MAX_STREAMS, etc.). However, the attacker sticks to using 0-RTT for packets it emits, even after the handshake is complete. simultaneously, the attacker creates many QUIC connections using the same Initial packet as that of step 2, and then sends the copied 0-RTT packets. The server will handle those and commit state. Because the client never responds with an 1-RTT ACK, the connection will eventually die. However, a client can create a connection with huge PTO (by delaying Initial packets) to have enough time to let the server accept many packets. IMO it is preferable to allow less frames in 0-RTT packets, because it narrows the attack vector. PS. Anyways I think it's worth clarifying how to minimize stream-related states being created by 0-RTT replay attacks; left my comment URL
NAME QUIC uses for that purpose. You can't receive a flow control update, so whatever value you have for that determines how much 0-RTT you can send. In thinking through this, I realized that the client can't send 0-RTT after receiving 1-RTT from the server. A client won't have the keys until it has the entire handshake flight from the server, at which point it has to switch to 1-RTT keys. So this all comes down to the same calculus we have in TLS/TCP, after you have spent a little bit of time on the question of RESET_STREAM and its effect on long-term application state.
NAME My understanding is that an attacker acting as a client can receive flow control updates (in 1-RTT packets), and use the updated value for emitting additional 0-RTT packets. Something like following: client obtains to a server and obtains session ticket and TP client reconnects, sends 0-RTT packets containing a STREAM frame (id=0, off=TP.initialmaxdata-1, len=1) server sends handshake messages and a 1-RTT packet that contains MAXDATA (maximumdata=TP.initalmaxdata3), because it observes the client reaching the limit client sends a 0-RTT packet containing a STREAM frame (id=0, off=TP.initialmaxdata3-1, len=1) server sends another 1-RTT packet containing MAXDATA... client sends a 0-RTT packet containing a yet another STREAM frame...
At the point the client has 1-RTT keys for receiving, it will have the ability to send using 1-RTT keys. It can (and should) send its Finished too. Why would it send with 0-RTT keys?
Because the client here is an attacker. However, I'm not seeing as much threat related to this specific change -- this is something the client can do regardless (generating many connections and causing state to be allocated for each). So the attack here is to artificially create a large batch of (hopefully work-intensive) 0-RTT packets which are legitimate given a particular session ticket. Then open a large set of parallel connections using the same session ticket and dump all those 0-RTT packets into each connection. To guard against this, TLS 1.3 recommends . This defense is the same for QUIC, and lives in the TLS layer.
Is the concern here about a denial of service attack on the server? That is, the extra frames that it might receive, which aren't really limited in any way by flow control or other such limitations, might cause it to do too much work? This doesn't sound that interesting to me. There is always a risk when you accept 0-RTT that the client or data is bad. As NAME points out, you shouldn't be accepting the same 0-RTT multiple times. The recommendations in TLS 1.3 provide some tips on how to avoid that. But even if your server configuration doesn't allow you to detect that and stop it, the work you expend on 0-RTT packets is a cost that you take on. And you take on that work with the understanding of the associated risk of replay. Clients can always send far more stuff than you are prepared to process; servers just need to make the call. For instance, rejecting 0-RTT would be my first response to increasing server load. If a client chooses to send 0-RTT when 1-RTT keys are available to it, maybe we can allow servers to cut them off. That's maybe hard to police, but simple things like exceeding initial flow control might be detectable. Even if the server has provided more credit, the client should still be operating within the initial window for 0-RTT.
I like that defense. Just to sanity check, there shouldn't be a case in which the TLS handshake takes an extra round-trip and 0-RTT is accepted, right? Assuming that's true, saying that 0-RTT data MUST NOT exceed the initial connection flow control window (even if MAX_DATA frames are received in 1-RTT) and a pointer to the TLS 1.3 anti-replay protections seem like sufficient mitigation here.
Correct. TLS only takes an extra round trip with HelloRetryRequest, which results in 0-RTT being automatically rejected.
NAME The difference between a DoS using 1-RTT packets and 0-RTT packets is that the latter requires far less CPU cost on the attacker-side. This is because an attacker can just retransmit the same packets from different source tuples, whereas in 1-RTT case, an attacker needs to do expensive crypto operations (especially DH) for each connection it establishes. Therefore my preference goes to limiting the capability of 0-RTT. I like (maybe more than the approach I described in URL), because it clearly limits what a client can do using 0-RTT for streams. The remaining question is what we should do for non-stream-related frames. I still think that it makes sense to disallow the use of any non-stream-related frame in 0-RTT packets, considering the fact that: the frames allowed by 0-RTT and 1-RTT packets are nevertheless going to be different (CRYPTO, ACK) in practice there's no reason to send non-stream-related frames in 0-RTT packets potential security issues related to replay
FWIW, I think that is about formalizing a poorly-enforced constraint that we might have assumed already existed. It's not directly a solution to the problem were discussing. I personally don't think that the cost of processing frames is significant enough to build additional defenses for. Generic anti-DoS measures should suffice. But clearly that's not a universally held viewpoint. I think we should discuss this in Tokyo.
If I understand our correctly, this attack is only possible for a limited amount of time, namely 3 PTO after the server received the Client Initial:
NAME 3 PTO is not going to defend the server here, because an attacker can control the value. It can delay the Initial packets that it sends (carrying ACKs) so that PTO becomes huge.
If I understand correctly, the server switches to 1-RTT right after receiving the Client Initial, doesn't it? That means that PTO will have its initial value, and not be client-controlled. Obviously, this could be a problem on long RTT connections, so maybe that rule about dropping packets needs to be changed.
In the scenario that we are discussing (yeah the thread is getting long), the attacker sends the Initial packet that contains an ACK to the server's Initial. That means that if an attacker can for example send the ACK after 3 seconds (3 seconds of RTO!) to have an open window of additional 9 seconds to perform an attack.
I am concerned with the attack in which the 0-RTT data is replayed by a third party, maybe also with a replayed "retry token". Do we want more than 3 packets there? Do we really want to allow sending a large flow control window of data? Also, 0-RTT may very well be rejected by the server. Sending something that changes connection state looks weird, because all these frames can be dropped.
Tokyo proposal: limit allowed frames to stuff we know are useful. This leaves CONNECTIONCLOSE, NEWCONNECTIONID, PATHCHALLENGE, PATHRESPONSE, PING, and RETIRECONNECTIONID out for the moment, but if we identify a need, we would do the analysis regarding safety under replay attack and make a decision. This means that extensions would need to make their own assessment about safety. Note that ACK, CRYPTO, and NEWTOKEN are not allowed for other, existing reasons. What would be allowed: STREAM, RESETSTREAM, STOPSENDING, MAXDATA, MAXSTREAMDATA, MAXSTREAMID, DATABLOCKED, STREAMDATABLOCKED, STREAMIDBLOCKED, and PADDING. Still open debate between taking this and the PR that has been proposed.
I think we need a complementary analysis on expected server behavior. For example, if MAX DATA increase, does that mean sending that much data right now, or does that mean sending a small amount now, and wait until handshake complete to send more?
The congestion controller and the limitations on how many bytes can be sent to an unvalidated address are separate from flow control. What frames are permitted in 0-RTT has no bearing on those limits.
NAME to be clear - with , are you referring to the comment from Tokyo above, or the revised ?
This is an issue that never really resolved in Tokyo. It is that I was referring to. See also voluminous list discussion on the topic.
LGTM, one nit.Generally looks good. Just need to make sure CONNECTION_CLOSE is allowed everywhere.

Data is protected using a number of encryption levels:  <ins> Initial Keys </ins> Early Data (0-RTT) Keys level.  For instance, an implementation might bundle a Handshake message and an ACK for some Handshake data into the same packet.  <ins> Some frames are prohibited in different encryption levels, others cannot be sent.  The rules here generalize those of TLS, in that frames associated with establishing the connection can usually appear at any encryption level, whereas those associated with transferring data can only appear in the 0-RTT and 1-RTT encryption levels: </ins> PADDING frames MAY appear in packets of any encryption level. <ins> CRYPTO and CONNECTION_CLOSE frames MAY appear in packets of any encryption level except 0-RTT. </ins> ACK frames MAY appear in packets of any encryption level other than 0-RTT, but can only acknowledge packets which appeared in that packet number space.  <ins> All other frame types MUST only be sent in the 0-RTT and 1-RTT levels. </ins>  <ins> Note that it is not possible to send the following frames in 0-RTT for various reasons: ACK, CRYPTO, NEW_TOKEN, PATH_RESPONSE, and RETIRE_CONNECTION_ID. </ins> Because packets could be reordered on the wire, QUIC uses the packet type to indicate which level a given packet was encrypted under, as The keys used for packet protection are computed from the TLS secrets using the KDF provided by TLS.  In TLS 1.3, the HKDF-Expand-Label  <ins> function described in Section 7.1 of TLS13 is used, using the hash </ins> function from the negotiated cipher suite.  Other versions of TLS  <ins> MUST provide a similar function in order to be used with QUIC. </ins> The current encryption level secret and the label "quic key" are input to the KDF to produce the AEAD key; the label "quic iv" is used to derive the IV, see aead.  The header protection key uses the "quic  <ins> hp" label, see header-protect.  Using these labels provides key </ins> separation between QUIC and TLS, see key-diversity. The KDF used for initial secrets is always the HKDF-Expand-Label in hexadecimal notation.  Future versions of QUIC SHOULD generate a new salt value, thus ensuring that the keys are different for each version of QUIC.  This prevents a middlebox that only recognizes one  <ins> version of QUIC from seeing or modifying the contents of packets from future versions. </ins> The HKDF-Expand-Label function defined in TLS 1.3 MUST be used for Initial packets even where the TLS versions offered do not include All QUIC packets other than Version Negotiation and Retry packets are protected with an AEAD algorithm AEAD.  Prior to establishing a shared secret, packets are protected with AEAD_AES_128_GCM and a key  <ins> derived from the Destination Connection ID in the client's first </ins> Initial packet (see initial-secrets).  This provides protection against off-path attackers and robustness against QUIC version unaware middleboxes, but not against on-path attackers. The "extension_data" field of the quic_transport_parameters extension contains a value that is defined by the version of QUIC that is in use.  The quic_transport_parameters extension carries a  <ins> TransportParameters struct when the version of QUIC defined in QUIC- </ins> TRANSPORT is used. The quic_transport_parameters extension is carried in the ClientHello 9.1. <ins> As described in Section 8 of TLS13, use of TLS early data comes with an exposure to replay attack.  The use of 0-RTT in QUIC is similarly vulnerable to replay attack. Endpoints MUST implement and use the replay protections described in TLS13, however it is recognized that these protections are imperfect. Therefore, additional consideration of the risk of replay is needed. QUIC is not vulnerable to replay attack, except via the application protocol information it might carry.  The management of QUIC protocol state based on the frame types defined in QUIC-TRANSPORT is not vulnerable to replay.  Processing of QUIC frames is idempotent and cannot result in invalid connection states if frames are replayed, reordered or lost.  QUIC connections do not produce effects that last beyond the lifetime of the connection, except for those produced by the application protocol that QUIC serves. TLS session tickets and address validation tokens are used to carry QUIC configuration information between connections.  These MUST NOT be used to carry application semantics.  The potential for reuse of these tokens means that they require stronger protections against replay. A server that accepts 0-RTT on a connection incurs a higher cost than accepting a connection without 0-RTT.  This includes higher processing and computation costs.  Servers need to consider the probability of replay and all associated costs when accepting 0-RTT. Ultimately, the responsibility for managing the risks of replay attacks with 0-RTT lies with an application protocol.  An application protocol that uses QUIC MUST describe how the protocol uses 0-RTT and the measures that are employed to protect against replay attack.  An analysis of replay risk needs to consider all QUIC protocol features that carry application semantics. Disabling 0-RTT entirely is the most effective defense against replay attack. QUIC extensions MUST describe how replay attacks affects their operation, or prohibit their use in 0-RTT.  Application protocols MUST either prohibit the use of extensions that carry application semantics in 0-RTT or provide replay mitigation strategies. 9.2. </ins> A small ClientHello that results in a large block of handshake messages from a server can be used in packet reflection attacks to amplify the traffic generated by an attacker. attacker cannot forge them.  Put together, these defenses limit the level of amplification.  <ins> 9.3. </ins> QUIC, TLS, and HTTP/2 all contain messages that have legitimate uses in some contexts, but that can be abused to cause a peer to expend implementations SHOULD track redundant packets and treat excessive volumes of any non-productive packets as indicative of an attack.  <ins> 9.4. </ins> Header protection relies on the packet protection AEAD being a pseudorandom function (PRF), which is not a property that AEAD payloads and packet numbers MUST be free from side-channels that would reveal the packet number or its encoded size.  <ins> 9.5. </ins> In using TLS, the central key schedule of TLS is used.  As a result of the TLS handshake messages being integrated into the calculation


number space.  Separate packet number spaces ensures acknowledgement of packets sent with one level of encryption will not cause spurious retransmission of packets sent with a different encryption level. <del> Congestion control and RTT measurement are unified across packet number spaces. </del>  3.1.2. 5. <del> RTT is calculated when an ACK frame arrives by computing the difference between the current time and the time the largest acked packet was sent.  An RTT sample MUST NOT be taken for a packet that is not newly acknowledged or not ack-eliciting. </del>  When RTT is calculated, the ack delay field from the ACK frame SHOULD be limited to the max_ack_delay specified by the peer.  Limiting QUIC senders use both ack information and timeouts to detect lost packets, and this section provides a description of these algorithms. <del> Estimating the network round-trip time (RTT) is critical to these algorithms and is described first. </del> If a packet is lost, the QUIC transport needs to recover from that loss, such as by retransmitting the data, sending an updated frame,

Explicitly state that round trip time is abbreviated as RTT
In the recovery draft, RTT is briefly spelled out in section 5 but is used in several earlier sections. SRTT and PTO are not explained at all, it seems. TLP is spelled out. RTO has a section of its own but it referenced before that. The recovery doc cannot be a tutorial on transmission theory, but it would be helpful to list the key acronyms and possibly reference an RFC with more details. There are already sections summarising key variables in pseudocode. There are references at the end, but the reader would have to go through them all.
Yes, thanks for the note. I'm planning to do a cleanup of that doc soon.
I fixed defined RTT earlier in .

number space.  Separate packet number spaces ensures acknowledgement of packets sent with one level of encryption will not cause spurious retransmission of packets sent with a different encryption level.  <ins> Congestion control and round-trip time (RTT) measurement are unified across packet number spaces. </ins> 3.1.2. 5.  <ins> Round-trip time (RTT) is calculated when an ACK frame arrives by computing the difference between the current time and the time the largest acked packet was sent.  An RTT sample MUST NOT be taken for a packet that is not newly acknowledged or not ack-eliciting. </ins> When RTT is calculated, the ack delay field from the ACK frame SHOULD be limited to the max_ack_delay specified by the peer.  Limiting QUIC senders use both ack information and timeouts to detect lost packets, and this section provides a description of these algorithms.  If a packet is lost, the QUIC transport needs to recover from that loss, such as by retransmitting the data, sending an updated frame,


Required Insert Count to determine when it is safe to process the rest of the block. <del> If no references are made to the dynamic table, a value of 0 is encoded.  Alternatively, where the Required Insert Count is greater than zero, the encoder transforms it as follows before encoding: </del>  Here "MaxEntries" is the maximum number of entries that the dynamic table can have.  The smallest entry has empty name and value strings

This makes the encoding and decoding pseudo-code more symmetric. "Alternatively" might not be the best word here, because it might imply that the implementation has a choice. In a section about encoding Required Insert Count, it is more pure to talk about Required Insert Count being zero than to talk about whether references are made to the dynamic table.
Also, CI failed because the line is too long

Required Insert Count to determine when it is safe to process the rest of the block.  <ins> The encoder transforms the Required Insert Count as follows before encoding: </ins> Here "MaxEntries" is the maximum number of entries that the dynamic table can have.  The smallest entry has empty name and value strings


Each header block contains a Required Insert Count, the lowest possible value for the Insert Count with which the header block can <del> be decoded.  For a header block with no references to the dynamic table, the Required Insert Count is zero. </del>  When the Required Insert Count is zero, the frame contains no references to the dynamic table and can always be processed becomes greater than or equal to the Required Insert Count for all header blocks the decoder has started reading from the stream. <del> If the decoder encounters a header block where the largest Absolute Index used is not equal to the largest value permitted by the Required Insert Count, it MAY treat this as a stream error of type HTTP_QPACK_DECOMPRESSION_FAILED. </del> The SETTINGS_QPACK_BLOCKED_STREAMS setting (see configuration) specifies an upper bound on the number of streams which can be blocked.  An encoder MUST limit the number of streams which could

Currently the draft is inconsistent: if the Required Insert Count value in a header block is smaller than dictated by the referenced dynamic table entries, {{overview-hol-avoidance}} says the decoder MAY treat this as a stream error, while {{invalid-references}} says it MUST. This PR seeks to clarify this.

Each header block contains a Required Insert Count, the lowest possible value for the Insert Count with which the header block can  <ins> be decoded.  For a header block with references to the dynamic table, the Required Insert Count is one larger than the largest Absolute Index of all referenced dynamic table entries.  For a header block with no references to the dynamic table, the Required Insert Count is zero. If the decoder encounters a header block with a Required Insert Count value larger than defined above, it MAY treat this as a stream error of type HTTP_QPACK_DECOMPRESSION_FAILED.  If the decoder encounters a header block with a Required Insert Count value smaller than defined above, it MUST treat this as a stream error of type HTTP_QPACK_DECOMPRESSION_FAILED as prescribed in invalid-references. </ins> When the Required Insert Count is zero, the frame contains no references to the dynamic table and can always be processed becomes greater than or equal to the Required Insert Count for all header blocks the decoder has started reading from the stream.  The SETTINGS_QPACK_BLOCKED_STREAMS setting (see configuration) specifies an upper bound on the number of streams which can be blocked.  An encoder MUST limit the number of streams which could


reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently. <del> Endpoints that use connection IDs with length greater than zero could have their activity correlated if their peers keep using the same destination connection ID after migration.  The goal is to ensure that packets sent on different paths cannot be correlated.  To </del>  fulfill this privacy requirement, endpoints that initiate migration and use connection IDs with length greater than zero SHOULD provide their peers with new connection IDs before migration.

As discussed in Tokyo, forcing your peer to change a connection ID is still possible, but we would require a change in port in order to effect that. .
I don't remember this being what we agreed, and it's not necessary. You only need to change when you see a new path, not after a new CID. That's how we got rid of counting to infinity.
I'm not sure it is always well-defined when you are on a new path and you risk accidental linkage. I believe this was the thinking behind this.
IIRC, NAME is correct; we agreed to do this on peer address change. If you don't know you've been rebound and you reuse a CID, you've already linked yourself; the peer changing CID in response is pointless. If you either anticipate a rebinding or proactively change ports, and change both CID and address simultaneously, then it doesn't matter which of those triggers the peer to switch. If you switch CID without changing address, the address links you with fairly high probability anyway, so it doesn't really matter if the peer switches.
The recommendation exists to avoid linkability across a non-migrating change in connection ID.
I don't think that that's particularly useful, and it seems to create a bunch of new problems.
My recollection is what NAME says. I remember discussing that changing CID on both sides allows a flow to be broken up into flowlets, but if the address is the same on both sides, that's a pretty clear signal anyways. I am fine with requiring a change only if the peer's CID and address change.
That is not sufficient because the client could be migrating from domestic wifi to mobile, to train wifi, while the server always sees the IP address of the load balancer.
In this case the LB and the server should be regarded as the same thing, and it's the LB's job to convey that somehow.
FWIW, it helps when you have multiple connections coalesced onto a 5-tuple. An endpoints rotates the sending CIDs of all the connections at once, and that triggers the peer to rotate all of it's sending CIDs at once. Then, it becomes very hard for an observer to track the individual connections.
Tokyo:
That being (I'm closing ).
This was

reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently.  <ins> Endpoints that supply connection IDs with length greater than zero could have their activity correlated if their peers keep using the same destination connection ID after migration.  To ensure that packets sent on different paths cannot be correlated, endpoints SHOULD provide with new connection IDs before peers migrate.  To </ins> fulfill this privacy requirement, endpoints that initiate migration and use connection IDs with length greater than zero SHOULD provide their peers with new connection IDs before migration.


Using the Length field, a sender can coalesce multiple QUIC packets into one UDP datagram.  This can reduce the number of UDP datagrams <del> needed to complete the cryptographic handshake and starting sending data.  Receivers MUST be able to process coalesced packets. </del>  Coalescing packets in order of increasing encryption levels (Initial, 0-RTT, Handshake, 1-RTT) makes it more likely the receiver will be The considerations for processing ICMP messages in the previous section also apply if these messages are used by DPLPMTUD.  15. QUIC versions are identified using a 32-bit unsigned number.

Describe PMTU probing that includes source connection ID for routing ICMP back the correct endpoints. This should
Sorry, but this sounds like a huge hack - coalescing with unused handshake packets. It also sounds like it is at risk of being ignored. I'd rather see a specific permission for a probe packet with a longer header as the only long header permissible throughout the connection, and possibly require it to not be coalesced with anything.
I tend to agree. Yet, the described method is what was determined at Kista to be the best way, and this was reconfirmed in Tokyo. I am happy to rewrite using a special long header packet type, if the wg decides to pivot toward that. At the end of the day, having something that works is what matters the most.
NAME My understanding is that the approach is perfectly fine; see what a receiver is supposed to do in section 12.2. I am opposed to introducing a special type of long header packet just for this purpose; that would be an unnecessary complexity. OTOH I agree that servers might be interesting in greasing this feature (by coalescing every 1-RTT packet that the server sends with an invalid long header packet, for small portion of the connections it handles).
NAME Coalescing something with handshake packets is no different than not coalescing. You are sending long header packets post 1-RTT. Disregarding the actual text, I still find it inelegant and error prone.
Another point, which is important at least to me: The entire idea about not having long headers post 1-RTT, and to make a note of it, is to allow implementations to focus on short packets only for the reminder of the connection, and dropping all context about separate number spaces etc.. If that stops being the case, all the bootrapping mess comes back to haunt you. I'd switch to different packet processor post handshake so there is no ACK special handling etc. If you have a dedicated allowance for a probe packet, that is much easier to deal with because you know the context is the same is for the short headers.
NAME No special handling is necessary, aside from splitting the coalesced packets. The design does not require the receiver to actually handle the long header packet. Simply dropping it is fine, as long as the receiver processes the short header packet that is being coalesced with the long header packet. I understand the fact that you want to have an optimized path for short header packets. That's fine. OTOH, the receiver needs to handle a coalesced datagram that carries various types of long header packets and a 1-RTT packet, at least during the handshake. This is true even if you do not have a corresponding key (e.g. you need to "drop" a 0-RTT packet in a coalesced datagram even when you do not not have the 0-RTT key). Considering that, I am not sure if it a good idea to disable the logic to split coalesced datagram into multiple packets when dropping the Handshake key. Isn't it an additional complexity to disable the logic upon the completion of the handshake? FWIW, the specification currently states: Every QUIC packet that is coalesced into a single UDP datagram is separate and complete. Though the values of some fields in the packet header might be redundant, no fields are omitted. The receiver of coalesced QUIC packets MUST individually process each QUIC packet and separately acknowledge them, as if they were received as the payload of different UDP datagrams. For example, if decryption fails (because the keys are not available or any other reason), the the receiver MAY either discard or buffer the packet for later processing and MUST attempt to process the remaining packets. ().
To an extend yes, but if you do care about performance, it can be a simplification: You need the core functionality regardless, so it is mostly function calls to logic that must exist either way. And, it allows you to be more lazy performance wise when dealing with the complexities of the handshake where crypto overhead and low packet count does not make optimisations worthwhile. This means you can shift that focus to short packets. This gets into biblical citations, but ... >Every QUIC packet that is coalesced into a single UDP datagram is separate and complete. but it also says that you cannot have multiple connections, and you cannot have long headers post 1-RTT. Technically you can coalesce a packet "sent" before the packet being coalesced, but if you have seen a short header, you can reasonably assume that any long headers are of no interest and therefore drop processing of that datagram ASAP.
Some background that might illustrate why this is feasible: You can attach a state machine to the connection. Each state has function that is called on new input. Once the state enters short packet state, it can a be different function from the handshake related state handlers. So you just look up the CID, and call a function. The same goes for draining etc.
The coalescing logic requires you to skip long header packets after the handshake and process the coalesced packets in the UDP datagram. I.e. you need to wrap this is a loop. In Tokyo we decided that you can limit the number of times you will go through this loop per UDP datagram, if you want. (I think we said that the limit can be as low as 4.)
That's correct. You cannot coalesce QUIC packets belonging to multiple connections into the same UDP datagram. This is not changed. It is also correct that a long-header packet past the completion of the handshake is not valid. So you need to do with that long-header QUIC packet what you would do with invalid packets -- drop it. (That's why the text says that such packets will not elicit an acknowledgement.) But then you should keep processing coalesced QUIC packets arriving in the same datagram, per text of the spec.
I do not disagree, I'm just saying that, except for this probing edge case, you really have no motivation to keep processing such packets at that point in time. Anybody wanting piggyback other payload on meaningful short header content is probably playing some games. But you can't because of this special case.
But, if you actually drop the longer header and proceed to the short header probe, you loose your source ID, so for it to work, you need to go against the current spec and process long headers post 1-RTT. Furthermore, you go against an important design principle of keeping seperate packets separate. How can you be sure the preceding long packet contains the intended source ID, under strict interpretation of the protocol? Instead I'd be much simpler to include the source id in the probe packet somehow.
Umm, if you don't have the handshake keys anymore, how do verify the long packet. And if you don't, how can you trust that someone didn't throw in some other source id (not sure that would be an efficient attack, but still).
Better, you cannot send Handshake packets at all when you no longer have the keys, and if you could, they would be open to MiTM because they keys aren't really that secure since the handshake as already been accepted and therefore cannot fail. So that leaves sending handshake packets with 1-RTT keys, and I couldn't really find where this is prohibited, but I believe the idea is that each packet type identifies an encryption level.
The long header packet is a Handshake packet because the first octet identifies it as such. It will not decrypt and is not meant to be decrypted (only short header packets are valid at this point in the connection). I guess a little more text is needed to make this point clear.
NAME Regarding optimization, an endpoint would rarely send PMTU probes. Therefore, processing of the probe going into a slow path is not an issue. Almost all the packet that the receiver receives would be short header packets.
NAME the problem is not slow pathing rare packets, but fast pathing approved connections - so they never parse anything but short headers or fail. However, if a probe uses a new CID this is indeed not a concern because the CID would not hit the fast path in the first place.
The probe is a part of the connection. Otherwise it will not elicit an ACK of the coalesced short packet. I am a bit confused that we are discussing the cost of a single if statement.
But it will use a dedicated connection ID. Yes, but it still impacts the design because the first thing I would do would be to look up bytes 1-4 in a hash table and call a handler function that has no context for handshake. So it will now have to add an indirection and process packets it does not understand. It can't be the same logic as the one that handles handshakes, because it would start by verifying the packet. Or that initial lookup would would have to first check the packet type and go slow path, but the context must be looked up by ID, and the general slow path expects context, or it will drop packets. But you can't do that here, because there is no context. So you have to special handle that. So performance-wise it would be survivable, just pretty ugly.
I also don't understand exactly the purpose of the source id: Is it a) for the ending endpoint to make sense of ICMP, or b) do you expect ICMP to be routed based on the QUIC headers? If it is a), why would you not want the source ID to be authenticated? I could see some interesting attacks sending bogus ICMP packets to manipulate the connection. For example by network operators that want to bandwidth limit QUIC packets. That is, if enough of the copy survives to have anything that can be authenticated.
Can we PLEASE have these discussions on the issue and not the PR?
Thanks for the PR, NAME
They don't have a length field, so the only place they could go is at the end of a packet. As the spec currently stands, it's valid to send a packet like this. There's little value in doing that. Do we want to continue to allow this?
My understanding is that they are implicitly forbidden, because coalescing packets belonging to different connections are forbidden, and because there’s nothing to coalesce with when either of the two types of the packets is sent. I am not sure if we need to be more clear on that, considering the fact we generally use MAY for the discarding rules for coalesced packets.
VN certainly causes a new connection based on discussions in Tokyo. Retry does not cause a new connection(ie: same ClientHello). Clarifying this seems wise if it's not already clear.
Yes, however when a Retry packet is being sent, there's nothing a server can send along with. Regarding if we need to clarify, while I do not think it's absolutely necessary, I can see that people might want to have it stated considering the fact that we do utilize corner case rules of coalesced packets (see ).
So, is this editorial?
I'm with NAME but I'm still wondering: what is the clarification that we can provide?
NAME Simply clarifying that "VN and Retry packets will never be coalesced" as part of the changes we would make in might be a good idea. We do not need to use a RFC 2119 word here due to the editorial nature of the issue.
The PR looks good to me, but I think we might want to consider changes like below to better clarify how the design works.

Using the Length field, a sender can coalesce multiple QUIC packets into one UDP datagram.  This can reduce the number of UDP datagrams  <ins> needed to complete the cryptographic handshake and start sending data.  This can also be used to construct PMTU probes (see pmtu- probes-src-cid).  Receivers MUST be able to process coalesced packets. </ins> Coalescing packets in order of increasing encryption levels (Initial, 0-RTT, Handshake, 1-RTT) makes it more likely the receiver will be The considerations for processing ICMP messages in the previous section also apply if these messages are used by DPLPMTUD. <ins> 14.3.1. Endpoints that rely on the destination connection ID for routing QUIC packets are likely to require that the connection ID be included in PMTU probe packets to route any resulting ICMP messages (icmp-pmtud) back to the correct endpoint.  However, only long header packets (long-header) contain source connection IDs, and long header packets are not decrypted or acknowledged by the peer once the handshake is complete.  One way to construct a PMTU probe is to coalesce (see packet-coalesce) a Handshake packet (packet-handshake) with a short header packet in a single UDP datagram.  If the UDP datagram reaches the endpoint, the Handshake packet will be ignored, but the short header packet will be acknowledged.  If the UDP datagram elicits an ICMP message, that message will likely contain the source connection ID within the quoted portion of the UDP datagram. </ins> 15. QUIC versions are identified using a 32-bit unsigned number.


10.2. <del> If the idle timeout is enabled, a connection that remains idle for longer than the advertised idle timeout (see transport-parameter- definitions) is closed.  A connection enters the draining state when the idle timeout expires. </del>  Each endpoint advertises its own idle timeout to its peer.  An enpdpoint restarts any timer it maintains when a packet from its peer

Changes are: define idle timeout as skip draining period when idle timeout expires
NAME -- yup, I realized it after, so discussed on slack (you're on the thread)
Currently, the t is in units of seconds. This seems to be too restrictive and not very future proof. Additionally, from an API perspective, most other timers are settable on the millisecond scale, so it creates a kind of ugly discrepancy between all the different timers settable by the app if just one of them has units of seconds. I propose we change the units from seconds to milliseconds. The change both for implementations and spec should be minimal, and it makes things (timers) a little more consistent.
This seems sensible to me both for consistency and I can imagine cases when one would want to specify a fraction(ie: 1/2 of a second).
Agreed. A second is an eternity for a intra-datacenter connection for example. For future proofing all time units should be at least msec IMO.
Don't we want to specify a minimum value now?
No, I don't think so. Even before, if you had a greater than 1 sec RTT (somehow...) if you set an idle timeout of 1 second it would cause you problems. I think bottom line you be smart about your idle timeout values. If you set too small a value you can unnecessarily kill your own connection.
As NAME says, actual idle timeout depends on RTT, and that’s what we have adopted in .
This SGTM.
I support this
I think this has rough consensus. Tagging as such and reassigning.
IIUC, the primary reason of having STATELESS_RESET is to let the client notice that the connection is dead immediately, so that it can retransmit the request using a new connection. However, when a server receives a request immediately after it's idle close timer has expired, it cannot send any packet in response, because it is in the draining period. Is this the expected behavior? IMO, we should always allow a server to respond whenever it receives a packet after the idle timeout has expired. One way of allowing that is to let an endpoint immediately discard the connection state when the idle close timer expires.
As long as the idle timeout is longer than the draining period would be, I think that this is right. The text was there to allow for the case where a peer attempted to restart use of the connection near the end of the idle timeout period, but was unsuccessful. On balance, a stateless reset is better in that case.
Just checking what my code does: at the end of the idle period, the server just gets rid of the connection. No message, no draining period. Maybe this is a bug, but it felt right...
Tokyo conclusion: Forbidding the server from responding in this window is not great. We will drop the closing/draining period after an idle timeout and have the endpoint just drop state. NAME suggested that you could send the CONNECTION_CLOSE that you might have otherwise sent, but it is easier to just shortcut the closing period.
Amendment based on discussion: suggest setting the idle timeout to to avoid pathological cases.

10.2.  <ins> If the idle timeout is enabled, a connection is silently closed and the state is discarded when it remains idle for longer than both the advertised idle timeout (see transport-parameter-definitions) and three times the current Probe Timeout (PTO). </ins> Each endpoint advertises its own idle timeout to its peer.  An enpdpoint restarts any timer it maintains when a packet from its peer


perform path validation (migrate-validate) if it detects a change in the IP address of its peer.  This document limits migration of connections to new client addresses, except as described in preferred-address.  Clients are responsible for initiating all migrations.  Servers do not send non- 10. <del> Connections should remain open until they become idle for a pre- negotiated period of time.  A QUIC connection, once established, can be terminated in one of three ways: </del>  idle timeout (idle-timeout) stateless reset (stateless-reset)  10.1. The closing and draining connection states exist to ensure that

NAME this is annoyingly close to done. Would you mind?
NAME : Take a look.
LGTM

perform path validation (migrate-validate) if it detects a change in the IP address of its peer. <ins> When an endpoint has no validated path on which to send packets, it MAY discard connection state.  An endpoint capable of connection migration MAY wait for a new path to become available before discarding connection state. </ins> This document limits migration of connections to new client addresses, except as described in preferred-address.  Clients are responsible for initiating all migrations.  Servers do not send non- 10.  <ins> An established QUIC connection can be terminated in one of three ways: </ins> idle timeout (idle-timeout) stateless reset (stateless-reset) <ins> An endpoint MAY discard connection state if it does not have a validated path on which it can send packets (see migrate-validate). </ins> 10.1. The closing and draining connection states exist to ensure that


6.4. <del> To correctly implement congestion control, a QUIC sender tracks every ack-eliciting packet until the packet is acknowledged or lost.  It is expected that implementations will be able to access this information by packet number and crypto context and store the per-packet fields (sent-packets-fields) for loss recovery and congestion control. After a packet is declared lost, it SHOULD be tracked for an amount of time comparable to the maximum expected packet reordering, such as 1 RTT.  This allows for detection of spurious retransmissions. Sent packets are tracked for each packet number space, and ACK processing only applies to a single space. 6.4.1. The packet number of the sent packet. A boolean that indicates whether a packet is ack-eliciting.  If true, it is expected that an acknowledgement will be received, though the peer could delay sending the ACK frame containing it by up to the MaxAckDelay. A boolean that indicates whether the packet counts towards bytes in flight. A boolean that indicates whether the packet contains cryptographic handshake messages critical to the completion of the QUIC handshake.  In this version of QUIC, this includes any packet with the long header that includes a CRYPTO frame. The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC framing overhead. The time the packet was sent. 6.5. 6.5.1. Constants used in loss recovery are based on a combination of RFCs, papers, and common practice.  Some may need to be changed or negotiated in order to better suit a variety of environments. Maximum reordering in packets before packet threshold loss detection considers a packet lost.  The RECOMMENDED value is 3. Maximum reordering in time before time threshold loss detection considers a packet lost.  Specified as an RTT multiplier.  The RECOMMENDED value is 9/8. Timer granularity.  This is a system-dependent value.  However, implementations SHOULD use a value no smaller than 1ms. The RTT used before an RTT sample is taken.  The RECOMMENDED value is 100ms. 6.5.2. Variables required to implement the congestion control mechanisms are described in this section. Multi-modal timer used for loss detection. The number of times all unacknowledged CRYPTO data has been retransmitted without receiving an ack. The number of times a PTO has been sent without receiving an ack. The time the most recent ack-eliciting packet was sent. The time the most recent crypto packet was sent. The packet number of the most recently sent packet. The largest packet number acknowledged in the packet number space so far. The most recent RTT measurement made when receiving an ack for a previously unacked packet. The smoothed RTT of the connection, computed as described in The RTT variance, computed as described in The minimum RTT seen in the connection, ignoring ack delay. The maximum amount of time by which the receiver intends to delay acknowledgments, in milliseconds.  The actual ack_delay in a received ACK frame may be larger due to late timers, reordering, or lost ACKs. The time at which the next packet will be considered lost based on exceeding the reordering window in time. An association of packet numbers to information about them. Described in detail above in tracking-sent-packets. 6.5.3. At the beginning of the connection, initialize the loss detection variables as follows: 6.5.4. After a packet is sent, information about the packet is stored.  The parameters to OnPacketSent are described in detail above in sent- packets-fields. Pseudocode for OnPacketSent follows: 6.5.5. When an ACK frame is received, it may newly acknowledge any number of packets. Pseudocode for OnAckReceived and UpdateRtt follow: 6.5.6. When a packet is acknowledged for the first time, the following OnPacketAcked function is called.  Note that a single ACK frame may newly acknowledge several packets.  OnPacketAcked must be called once for each of these newly acknowledged packets. OnPacketAcked takes one parameter, acked_packet, which is the struct detailed in sent-packets-fields. Pseudocode for OnPacketAcked follows: 6.5.7. QUIC loss detection uses a single timer for all timeout loss detection.  The duration of the timer is based on the timer's mode, which is set in the packet and timer events further below.  The function SetLossDetectionTimer defined below shows how the single timer is set. This algorithm may result in the timer being set in the past, particularly if timers wake up late.  Timers set in the past SHOULD fire immediately. Pseudocode for SetLossDetectionTimer follows: 6.5.8. When the loss detection timer expires, the timer's mode determines the action to be performed. Pseudocode for OnLossDetectionTimeout follows: 6.5.9. DetectLostPackets is called every time an ACK is received and operates on the sent_packets for that packet number space.  If the loss detection timer expires and the loss_time is set, the previous largest acknowledged packet is supplied. Pseudocode for DetectLostPackets follows: 6.6. </del> The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet. Exceptions follow. sender should not consider itself application limited if it would have fully utilized the congestion window without pacing delay. <del> 7.10. 7.10.1. Constants used in congestion control are based on a combination of RFCs, papers, and common practice.  Some may need to be changed or negotiated in order to better suit a variety of environments. The sender's maximum payload size.  Does not include UDP or IP overhead.  The max packet size is used for calculating initial and minimum congestion windows.  The RECOMMENDED value is 1200 bytes. Default limit on the initial amount of data in flight, in bytes. Taken from RFC6928.  The RECOMMENDED value is the minimum of 10 * kMaxDatagramSize and max(2* kMaxDatagramSize, 14600)). Minimum congestion window in bytes.  The RECOMMENDED value is 2 * kMaxDatagramSize. Reduction in congestion window when a new loss event is detected. The RECOMMENDED value is 0.5. Number of consecutive PTOs required for persistent congestion to be established.  The rationale for this threshold is to enable a sender to use initial PTOs for aggressive probing, as TCP does with Tail Loss Probe (TLP) TLP RACK, before establishing persistent congestion, as TCP does with a Retransmission Timeout (RTO) RFC5681.  The RECOMMENDED value for kPersistentCongestionThreshold is 2, which is equivalent to having two TLPs before an RTO in TCP. 7.10.2. Variables required to implement the congestion control mechanisms are described in this section. The highest value reported for the ECN-CE counter by the peer in an ACK frame.  This variable is used to detect increases in the reported ECN-CE counter. The sum of the size in bytes of all sent packets that contain at least one ack-eliciting or PADDING frame, and have not been acked or declared lost.  The size does not include IP or UDP overhead, but does include the QUIC header and AEAD overhead.  Packets only containing ACK frames do not count towards bytes_in_flight to ensure congestion control does not impede congestion feedback. Maximum number of bytes-in-flight that may be sent. The time when QUIC first detects a loss, causing it to enter recovery.  When a packet sent after this time is acknowledged, QUIC exits recovery. Slow start threshold in bytes.  When the congestion window is below ssthresh, the mode is slow start and the window grows by the number of bytes acknowledged. 7.10.3. At the beginning of the connection, initialize the congestion control variables as follows: 7.10.4. Whenever a packet is sent, and it contains non-ACK frames, the packet increases bytes_in_flight. 7.10.5. Invoked from loss detection's OnPacketAcked and is supplied with the acked_packet from sent_packets. 7.10.6. Invoked from ProcessECN and OnPacketsLost when a new congestion event is detected.  May start a new recovery period and reduces the congestion window. 7.10.7. Invoked when an ACK frame with an ECN section is received from the peer. 7.10.8. Invoked by loss detection from DetectLostPackets when new packets are detected lost. </del> 8. 8.1.

This seems like an unwieldy PR, so I'll state what I've done. I've simply moved all pseudocode to the appendix with no changes to text, except for the addition of a single sentence at the beginning of the appendices. It might be easier to see the rendered draft . Subsequent PRs will clean each part up.
This is a good start. I expect some of the text in the pseudocode sections will need to move up, but one step at a time.

6.4.  The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet. Exceptions follow. sender should not consider itself application limited if it would have fully utilized the congestion window without pacing delay.  8. 8.1.


handshake.  An endpoint which has sent this transport parameter, but detects that a peer has nonetheless migrated to a different network MAY treat this as a connection error of type INVALID_MIGRATION.  Not all changes of peer address are intentional migrations.  The peer could experience NAT rebinding: a change of address due to a need to ensure that connections IDs they provide cannot be linked by any other entity. <del> This eliminates the use of the connection ID for linking activity from the same connection on different networks.  Header protection ensures that packet numbers cannot be used to correlate activity. This does not prevent other properties of packets, such as timing and size, from being used to correlate activity. </del>  <del> Clients MAY move to a new connection ID at any time based on implementation-specific concerns.  For example, after a period of network inactivity NAT rebinding might occur when the client begins sending data again. </del>  A client might wish to reduce linkability by employing a new connection ID and source UDP port when sending traffic after a period reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently. <del> Endpoints that use connection IDs with length greater than zero could have their activity correlated if their peers keep using the same destination connection ID after migration.  Endpoints that receive packets with a previously unused Destination Connection ID SHOULD change to sending packets with a connection ID that has not been used on any other network path.  The goal here is to ensure that packets sent on different paths cannot be correlated.  To fulfill this privacy requirement, endpoints that initiate migration and use connection IDs with length greater than zero SHOULD provide their peers with new connection IDs before migration. If both endpoints change connection ID in response to seeing a change in connection ID from their peer, then this can trigger an infinite sequence of changes. </del>  9.6.

Based on , which should merge soon.
I think this strikes a good balance. It isn't practical to forbid migration without CID when NAT's do it behind your back, but allowing it for intentional migration would work against networks attempting to move towards safer modes of operation in the future, some which might only make sense if QUIC does indeed require new CID's on migration. At some point with IPv6, NAT rebinding might be seen the same way as older insecure WLAN standards.
This seems to have consensus, tagging as such and reassigning.
Removing tag as it needs to be canvassed on-list (and attached to an issue, not a PR).
I'm sorry if I misunderstood the new process, but I don't think your PR says that an issue must be discussed on the mailing list? Doesn't it say that the discussion can happen on the list or on the issue?
NAME discussion can happen in either place, but we've always checked with the list before declaring consensus (which is what records). In the currently operative process, that's happening (admittedly sporadically) after the drafts are published.
NAME NAME : The associated issue is and there's been a . There seems to be some disagreement on what needs to happen next. Specifically, some folks think this PR is an editorial one, and some want to open up the issue as a design issue for discussion.
is a design issue. If this is just editorial and no one disputes that, you can apply it as that, but it can't
is only marked design because there is debate, therefore, I conclude that we need chairs to help us. I requested that close to 2 weeks ago.
The requirement previously existed, but was accidentally dropped during one of the CID rewrites. The underlying question is whether we had consensus to remove the requirement previously -- if so, then reversing ourselves requires consensus. If not, then it's just fixing an accidental deletion.
If the PR that removed it doesn't have a note about removing it, I'd consider it an accidental deletion(which is what I suspect).
I thought the overall requirement for changing CIDs was , so it doesn't seem like the issue is around adding/removing a requirement, but rather the rest of the accompanying text here? Otherwise, not sure how this isn't entirely editorial, since the main requirement is already in place.
Currently the section 5.1(URL) says: "An endpoint whose peer has selected a zero-length connection ID MUST continue to use a zero-length connection ID for the lifetime of the connection and MUST NOT send packets from any other local address." In practice, 0 length connection IDs are only expected to be used when the receiver's IP/port identifies the connection, because otherwise QUIC connections cannot survive NAT rebinding. Given 0 byte connection IDs are not a privacy concern, the only reason I've heard to prohibit this is that we want to ensure servers never use trial decryption to deal with explicit migration. I don't believe that's a compelling reason, given there are existing use cases such as Peer-to-peer(ie: WebRTC) where we might want to use 0-byte connection IDs, but supporting migration may also be valuable. And I don't think any even small-scale server is going to use trial decryption to handle migration due to the excessive CPU cost. There is already a transport parameter to indicate connection migration is not supported, so the peer should have a clear signal about whether they can migrate with a 0-byte CID.
I think I agree that we can remove MUST NOT, assuming that we would recommend (i.e. SHOULD) servers using 0-byte CID to set the disable migration flag.
The problem here is that any system that uses this capability (migrating while sending no connection ID), would be unreliable. To make it reliable, we would need to add signaling. Is this a feature you really need for QUICv1?
Oh, I see, the idea is to rely on DISABLE_MIGRATION. That is probably OK.
As a general piece of advice, I would not advise any endpoint(ie: server) receiving multiple connections on a given IP/port use 0 byte CIDs. And if a 'server' did want to do that, it should disable migration explicitly.
NAME Um, isn't it normal for servers (QUIC or otherwise) to listen on IP/port. Do you mean multiple connections from same client IP/port?
If servers want to terminate multiple connections on an IP/port(as is typical), they either should use CID lengths > 0 OR disable connection migration. If they use a CID length of 0, they need to expect that NAT rebinding is going to terminate the connection, but they may be ok with that.
... or these servers may be ok with sending PINGs as necessary.
If a connection receives a garbage packet, it can be easily rejected based on the (lack of / unknown) CID. Only someone on-path will be able to observe the CID in use, so only on-path attackers can make you attempt to decrypt garbage. If you use a zero-length CID, your CID is very easy to guess. The restriction on migrating means that an attacker must spoof the remote port and IP in order to perform this attack, which means being on-path, which brings zero-length connection IDs up to equivalent security. If we make this change, anyone can make you attempt to decrypt garbage packets. Are we sure we want that?
On that account, it should be a requirement that CID's are compared constant time, otherwise they are easy to guess anyway.
Discussed in Prague; flag for v2.
In fixing , I discovered that we don't actually mandate this. It's a privacy requirement that I thought we had previously agreed to, but it appears as though we missed capturing it properly.
I do not think it is wise to prohibit connection migration, when there are no available connection IDs. This is something that the client is fully capable of deciding for itself -- to migrate using an old connection ID or to reconnect instead. I would go with MUST use, if there is one available. Otherwise, privacy-conscious clients SHOULD reconnect.
It looks like it’s been moved around a lot, but 5.1.2 says Historically, that’s been under the CID sections, not the migration sections, which made it kind of hard for me to dig up. Re the proposed diff: >An endpoint that exhausts available connection IDs can only migrate if the peer chooses to use receive packets with zero-length connection IDs. Does that conflict at all with the below text in 5.1?
Thanks for finding that NAME That supports the change being editorial-only. As for the perceived conflict, yeah, the text is not ideal. I'll find some way to reword it. Perhaps by observing that you can never run out of zero-length connection IDs. NAME allowing a peer to exhaust connection IDs to maintain linkability across migrations is an attack that I'd rather we not have.
I'd not worry about the peer. But I would, indeed, worry about a man-on-a-side exhausting your reserve of connection IDs by pretending to initiate peer migration events. So this is a valid concern. Of course, a violation of this MUST is indistinguishable from NAT rebinding/tethering migration and thus is not detectable by the peer. So this MUST is really just a recommendation. It is a fine recommendation.
I agree with NAME here. In Tokyo we decided we want to avoid MUSTs that can't be enforced, and in this case it's reasonable for some clients to value migration over non-linkability when they run out of CIDs.
The intent is clearly to disallow avoidable migration on empty CIDs. Not running out would suggest keep using it, which is wrong. Otoh: if it is not required to use a new CID, it makes no sense to forbid empty CID, on migration.
The problem with migrating with an empty CID is that the recipient can't identify which connection it should be considered for. It's going to have to do trial decryption with the keys for all current 0LCID connections on that destination port if it wants to permit that. (In fact, I'm fairly sure we previously had text that said if you couldn't match a packet to exactly one connection upon receipt, you had to drop it; that seems to have been removed.) On a NAT rebinding, a server could say that if the IP matches and only the port has changed, it might still match a singular existing connection. That's only safe if the server is using a unique port for each connection, which seems unlikely.
NAME Good point.
Yes, I think that this was a mistake on my part. Clearly, if there is no connection ID on outward-bound packets, migration is generally inadvisable. I'll update the PR.
Superseded by , which does more, but does it better.
We ought to leave this issue open, and let it be closed by PR or by the chairs.
I don't remember this being what we agreed, and it's not necessary. You only need to change when you see a new path, not after a new CID. That's how we got rid of counting to infinity.
I'm not sure it is always well-defined when you are on a new path and you risk accidental linkage. I believe this was the thinking behind this.
IIRC, NAME is correct; we agreed to do this on peer address change. If you don't know you've been rebound and you reuse a CID, you've already linked yourself; the peer changing CID in response is pointless. If you either anticipate a rebinding or proactively change ports, and change both CID and address simultaneously, then it doesn't matter which of those triggers the peer to switch. If you switch CID without changing address, the address links you with fairly high probability anyway, so it doesn't really matter if the peer switches.
The recommendation exists to avoid linkability across a non-migrating change in connection ID.
I don't think that that's particularly useful, and it seems to create a bunch of new problems.
My recollection is what NAME says. I remember discussing that changing CID on both sides allows a flow to be broken up into flowlets, but if the address is the same on both sides, that's a pretty clear signal anyways. I am fine with requiring a change only if the peer's CID and address change.
That is not sufficient because the client could be migrating from domestic wifi to mobile, to train wifi, while the server always sees the IP address of the load balancer.
In this case the LB and the server should be regarded as the same thing, and it's the LB's job to convey that somehow.
FWIW, it helps when you have multiple connections coalesced onto a 5-tuple. An endpoints rotates the sending CIDs of all the connections at once, and that triggers the peer to rotate all of it's sending CIDs at once. Then, it becomes very hard for an observer to track the individual connections.
Tokyo:
That being (I'm closing ).
This was
Looks like good clarification, just one minor wording comment I thought that we had agreed that an endpoint that intentionally migrates has to use a new connection ID on the new path. But I couldn't find any "MUST" in the spec that said that. On the off chance that this wasn't just a mistake and there was some good reason not to mandate this, I'm bringing the discussion here. I've opened a design issue: URL The change I'm proposing is here: URL

handshake.  An endpoint which has sent this transport parameter, but detects that a peer has nonetheless migrated to a different network MAY treat this as a connection error of type INVALID_MIGRATION. <ins> Similarly, an endpoint MUST NOT initiate migration if its peer supplies a zero-length connection ID as packets without a Destination Connection ID cannot be attributed to a connection based on address tuple. </ins> Not all changes of peer address are intentional migrations.  The peer could experience NAT rebinding: a change of address due to a need to ensure that connections IDs they provide cannot be linked by any other entity.  <ins> At any time, endpoints MAY change the Destination Connection ID they send to a value that has not been used on another path. </ins>  <ins> An endpoint MUST use a new connection ID if it initiates connection migration.  Using a new connection ID eliminates the use of the connection ID for linking activity from the same connection on different networks.  Header protection ensures that packet numbers cannot be used to correlate activity.  This does not prevent other properties of packets, such as timing and size, from being used to correlate activity. Unintentional changes in path without a change in connection ID are possible.  For example, after a period of network inactivity, NAT rebinding might cause packets to be sent on a new path when the client resumes sending. </ins> A client might wish to reduce linkability by employing a new connection ID and source UDP port when sending traffic after a period reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently.  <ins> An endpoint that exhausts available connection IDs cannot migrate. To ensure that migration is possible and packets sent on different paths cannot be correlated, endpoints SHOULD provide new connection IDs before peers migrate. </ins> 9.6.