connection migration before the handshake is confirmed, as defined in section 4.1.2 of QUIC-TLS. <del> An endpoint also MUST NOT initiate connection migration if the peer sent the "disable_migration" transport parameter during the 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. 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. Not all changes of peer address are intentional migrations.  The peer could experience NAT rebinding: a change of address due to a middlebox, usually a NAT, allocating a new outgoing port or even a new outgoing IP address for a flow.  An endpoint MUST perform path validation (migrate-validate) if it detects any change to a peer's address, unless it has previously validated that address. </del>  When an endpoint has no validated path on which to send packets, it MAY discard connection state.  An endpoint capable of connection default of 25 milliseconds is assumed.  Values of 2^14 or greater are invalid. <del> The disable migration transport parameter is included if the endpoint does not support connection migration (migration).  Peers of an endpoint that sets this transport parameter MUST NOT send any packets, including probing packets (probing), from a local address or port other than that used to perform the handshake. This parameter is a zero-length value. </del>  The server's preferred address is used to effect a change in server address at the end of the handshake, as described in An endpoint detected an error with protocol compliance that was not covered by more specific error codes. <del> A peer has migrated to a different network when the endpoint had disabled migration. </del> An endpoint has received more data in CRYPTO frames than it can buffer.

Remove connection error response to a packets from a different source address when an endpoint has sent , clarify an endpoints options when prohibited packets are received, rename to , and remove error code.
Re: The value of this TP, I tend to agree with NAME pretty much all of the machinery must be implemented whether or not the TP is sent. But agreed - let's discuss elsewhere, since I think this particular change is in pretty good shape now.
I think PR is fine, though I would had added an explicit explanation of when a server may consider sending thing TP (other paths are likely to either not work or be very inefficient) and what a client that needs to migrate to a new path should do (recheck DNS from the new path and reconnect).
The primary use case for disableactivemigration is that load balancing infrastructure cannot support any changes to the 4-tuple, intentional or otherwise. So in this case if a peer still changes its tuples, the packets may hit a a new server endpoint that doesn't have any existing state, and will generate a stateless reset? I'd like to hold consensus on this issue until this is clarified in the text.
NAME I think we got to the point where either (a) the packets miraculously get to the right place and then the endpoint can choose to either honor them or drop them or (b) they don't, in which case that's sad, but that's what happens when your infrastructure doesn't handle NAT rebinding or any other changes to the 4-tuple. In neither case do you want to close the connection with an error, in case someone else has injected a packet with a different source address. Note URL >21.8. Stateless Reset Oracle >Stateless resets create a possible denial of service attack analogous to a TCP reset injection. This attack is possible if an attacker is able to cause a stateless reset token to be generated for a connection with a selected connection ID. An attacker that can cause this token to be generated can reset an active connection with the same connection ID. Should you be in a situation where the client will accept your stateless reset for a connection that's active if someone spoofs a source address and the packet ends up on a server that has no state? If that can happen, that seems like we should open a new issue, since that's something that can happen at any time (although seems to me to be pretty much covered by the text in 21.8). The point of this change is to be clear that the purpose of the TP is to instruct the endpoint sending the packets to not intentionally change its address or port, but that the server receiving the packets (if for whatever reason they still get there) needs to not close the connection if it sees that happening. It can drop the offending packets, or process them, but it's a problem if it does the previous behavior which was to close the connection.
NAME thanks for the detailed explanation. This looks good to me and I have no objections to consensus.
The mechanism for disabling migration might be clear, but it isn't clear what problems it addresses. Some more clarity on that would be useful. What I understand this to be intended for is server deployments that can't guarantee that packets from a new source address will be routed to the same server instance.
Having re-read the minutes from Tokyo, I would re-articulate the problem statement a bit. As you said, disable_migration is an indication from a server that the serving infrastructure cannot handle client address changes. I don't think there's any confusion on this purpose. The pending question IMO is what a server should do if it disables migration and then receives packets on the connection from a different address. NAME : Do you agree?
That sounds about right, but I draw two conclusions: There are some editorial improvements we might consider in light of this analysis. The pending question about what to do in those scenarios is complex, but the conclusion might just be a better explanation of the sorts of trade-offs that servers work on. As you imply, that might be editorial as well, but I suspect that we'll uncover ways in which conflicting interpretations of working group participants come to light.
Yes, that is what I had in mind as well. We may want to add some text around what a client might expect when it continues sending packets after switching networks to a server that has disable_migration set.
It is my understanding that "disablemigration set" means that packets sourced from a different IP or port are likely to be blackholed. Hence, the client SHOULD reconnect instead of attempting a connection migration. As for the servers that specified "disablemigration", they are free to blockhole packets for migrating connections or to honor connection migration (after an ordinary path validation).
NAME yes that sounds about right, but we probably want to specify that a server that cannot validate a new source MUST blackhole packets from that source.
NAME sure, though this is what naturally falls out from the text. There is already explicit text that source address validation is a MUST before a server can use a new path.
It may also be due to implementations not implementing path migration logic or maintain the necessary state, or servers that wish to use 0-length CIDs.
Discussed in London: to be resolved together with
NAME are you working on a PR here?
NAME I am happy to give it a read, if you have a PR ready. Or I could propose my own if that would not duplicate your work. Let me know.
Section 9 "Connection Migration" has this text: How can an endpoint distinguish "a peer has nonetheless migrated to a different network" vs "a man-on-a-side attacker has resent/raced a packet from a different IP"? If this hinges on path validation, an insidious attacker can actually own the new IP and proxy packets with PATHCHALLENGE back to the client and resend packets with PATHCHALLENGE_RESPONSE from its IP.
Yeah, that seems like too simple a DoS. It sort of highlights the need to implement much of the migration machinery if you consider the risk of NAT rebinding. Unless you plan to only permit remote address changes based on a "same network" heuristic.
Tokyo conclusion: Remove the text... But then, with our answer to , the endpoint that disables migration and then subsequently receives a packet from a new source address has two choices: it can do the full path validation and proceed to use the same path it can drop the packet The consequence of the latter is that the connection will time out, which is awful, but if the endpoint architecture is such that routing to the same instance is not possible, then packets will be dropped anyway. We already require, for the purposes of stateless reset oracle defense, that where routing is not consistent, that stateless resets not be generated for these cases. An endpoint can't send a stateless reset here, because that would produce the attack in a different form - our existing text on defense against stateless reset oracles would prohibit that anyway. We should perhaps rename the transport parameter to "disableexplicitmigration" or "disableactivemigration". Opened for clarifying this point.
Discussed in London: NAME to incorporate a resolution in or a separate PR
London: Also remove the error code, since it's no longer a protocol violation.
One editorial suggestion

connection migration before the handshake is confirmed, as defined in section 4.1.2 of QUIC-TLS.  <ins> An endpoint also MUST NOT send packets from a different local address, actively initiating migration, if the peer sent the "disable_active_migration" transport parameter during the handshake. An endpoint which has sent this transport parameter, but detects that a peer has nonetheless migrated to a different network MUST either drop the incoming packets on that path without generating a stateless reset or proceed with path validation and allow the peer to migrate. Generating a stateless reset or closing the connection would allow third parties in the network to cause connections to close by spoofing or otherwise manipulating observed traffic. Not all changes of peer address are intentional, or active, migrations.  The peer could experience NAT rebinding: a change of address due to a middlebox, usually a NAT, allocating a new outgoing port or even a new outgoing IP address for a flow.  An endpoint MUST perform path validation (migrate-validate) if it detects any change to a peer's address, unless it has previously validated that address. </ins> When an endpoint has no validated path on which to send packets, it MAY discard connection state.  An endpoint capable of connection default of 25 milliseconds is assumed.  Values of 2^14 or greater are invalid.  <ins> The disable active migration transport parameter is included if the endpoint does not support active connection migration (migration).  Peers of an endpoint that sets this transport parameter MUST NOT send any packets, including probing packets (probing), from a local address or port other than that used to perform the handshake.  This parameter is a zero-length value. </ins> The server's preferred address is used to effect a change in server address at the end of the handshake, as described in An endpoint detected an error with protocol compliance that was not covered by more specific error codes.  An endpoint has received more data in CRYPTO frames than it can buffer.


9.3. <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 processing resources without having any observable impact on the state of the connection.  If processing is disproportionately large in comparison to the observable effects on bandwidth or state, then this could allow a malicious peer to exhaust processing capacity without consequence. While there are legitimate uses for some redundant packets, implementations SHOULD track redundant packets and treat excessive volumes of any non-productive packets as indicative of an attack. 9.4. </del> Header protection relies on the packet protection AEAD being a pseudorandom function (PRF), which is not a property that AEAD algorithms guarantee.  Therefore, no strong assurances about the payloads and packet numbers MUST be free from side-channels that would reveal the packet number or its encoded size. <del> 9.5. </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 21.7.  An on-path attacker could manipulate the value of ECN codepoints in the IP header to influence the sender's rate.  RFC3168 discusses manipulations and their effects in more detail. an IP packet unless at least one QUIC packet in that IP packet is successfully processed; see ecn. <del> 21.8. </del>  Stateless resets create a possible denial of service attack analogous to a TCP reset injection.  This attack is possible if an attacker is for connections to time out.  However, this is acceptable only if the routing cannot be influenced by an attacker. <del> 21.9. </del>  This document defines QUIC Version Negotiation packets version- negotiation, which can be used to negotiate the QUIC version used packets MUST define a mechanism that is robust against version downgrade attacks. <del> 21.10. </del>  Deployments should limit the ability of an attacker to target a new connection to a particular server instance.  This means that client-

Issue observes that this text mentions HTTP/2, which isn't necessary. In reviewing it, I noticed that this talks about messages that do nothing, but not really about messages that do little (or that can be reversed to have net zero effect). So I expanded the text a little.
Thanks for improving the section's clarity. As a follow up question, when implementations decide that excessive packets is an attack, is there a recommended actionable (eg. sending reset, dropping the connection, do nothing) or would that be up for implementer's discretion?
Maybe this needs to be stated explicitly, but there is no point in continuing discussion with an attacker. So while it might be considered polite to send a connection error, it is also acceptable to just drop any further incoming packets. I'll add that here.
Under the section Peer Denial of Service of TLS draft, a referenced was made to HTTP/2. URL I was wonder if this is in fact referring to HTTP/3 or HTTP/2. Also curious why the section is labelled
That mention of h2 was a mistake. I've made a few changes after reviewing this. (The thing is a joke.)

9.3.  Header protection relies on the packet protection AEAD being a pseudorandom function (PRF), which is not a property that AEAD algorithms guarantee.  Therefore, no strong assurances about the payloads and packet numbers MUST be free from side-channels that would reveal the packet number or its encoded size.  <ins> 9.4. </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 21.7. <ins> QUIC and TLS both contain messages that have legitimate uses in some contexts, but that can be abused to cause a peer to expend processing resources without having any observable impact on the state of the connection. Messages can also be used to change and revert state in small or inconsequential ways, such as by sending small increments to flow control limits. If processing costs are disproportionately large in comparison to bandwidth consumption or effect on state, then this could allow a malicious peer to exhaust processing capacity. While there are legitimate uses for all messages, implementations SHOULD track cost of processing relative to progress and treat excessive quantities of any non-productive packets as indicative of an attack.  Endpoints MAY respond to this condition with a connection error, or by dropping packets. 21.8. </ins> An on-path attacker could manipulate the value of ECN codepoints in the IP header to influence the sender's rate.  RFC3168 discusses manipulations and their effects in more detail. an IP packet unless at least one QUIC packet in that IP packet is successfully processed; see ecn.  <ins> 21.9. </ins> Stateless resets create a possible denial of service attack analogous to a TCP reset injection.  This attack is possible if an attacker is for connections to time out.  However, this is acceptable only if the routing cannot be influenced by an attacker.  <ins> 21.10. </ins> This document defines QUIC Version Negotiation packets version- negotiation, which can be used to negotiate the QUIC version used packets MUST define a mechanism that is robust against version downgrade attacks.  <ins> 21.11. </ins> Deployments should limit the ability of an attacker to target a new connection to a particular server instance.  This means that client-


4.3.  Server push is an interaction mode introduced in HTTP/2 HTTP2 which permits a server to push a request-response exchange to a client in anticipation of the client making the indicated request.  This trades

Is it worth adding a reference to RFC 8441? That's an alternative that covers what people looking for this functionality were probably trying to do.
That's a good point, let me craft something up and I'll update the PR soon.
Ping NAME unless you'd like me to take it over.
I'll get an update out tomorrow
I'm actually not convinced that referencing 8441 will help clarify things here because the extension is H2 specific. No H3-equivalent exists (or at least, no H3 setting to enable the equivalent CONNECT behaviour exists).
I agree. I think referencing 8441 might just add to the confusion - even if something like 8441 certainly can be introduced.
Some others have shown interest for defining/framing this capability in HTTP/3. I think that activity should consider ore broadly what text makes sense in the core document.
Fair enough. Let's merge the rebased version, then, and leave the right way to do this for an H3 extension.
HTTP/3 makes no explicit comment about this. HTTP/2 prohibits it. So I think that we might want to add equivalent text. URL informational status code ([RFC7231], Section 6.2.2). multiplexed protocol.

4.3. <ins> HTTP/3 does not support the HTTP Upgrade mechanism (RFC7230, Section 6.7) or 101 (Switching Protocols) informational status code (RFC7231, Section 6.2.2). 4.4. </ins> Server push is an interaction mode introduced in HTTP/2 HTTP2 which permits a server to push a request-response exchange to a client in anticipation of the client making the indicated request.  This trades


client does not yet have 1-RTT keys.  If the probe timer expires before the client has 1-RTT keys, it is possible that the client may not have any crypto data to retransmit.  However, the client MUST <del> send a new packet, containing only PADDING frames if necessary, to allow the server to continue sending data.  If Handshake keys are available to the client, it MUST send a Handshake packet, and otherwise it MUST send an Initial packet in a UDP datagram of at least 1200 bytes. Because Initial packets containing only PADDING do not elicit an acknowledgement, they may never be acknowledged, but they are removed from bytes in flight when the client gets Handshake keys and the Initial keys are discarded. </del>  6.3.1.

which discusses sending an ack-eliciting packet instead of a PADDING only packet to unblock the server.
So, currently PING frames can only be sent in 0/1-RTT as per the TLS mapping draft URL AFAICT the issue of allowing PING frames in Initial/Handshake packets was discussed in URL which was in turn fixed by URL by saying to only send PADDING frames in probe packets. But then URL came along which changed the same text by saying that probes need to be ACK-eliciting. Notably it says: In the case where no other frame needs to be sent, but 1-RTT keys are not available, an implementation can send an empty CRYPTO frame to achieve the above as suggested by NAME so I proposed an editorial change in URL to expand the suggestion in -recovery to mention that, since I think it's something that would be easy to miss (and indeed, I don't think my implementation is the only one that got this wrong). However some people seem to prefer allowing PING frames in Initial/Handshake in the first place (which is a design change), so maybe we should discuss this more. That anyone knows, is there any reason why PING frames are not allowed in Initial/Handshake? Proposed change in .
While I am fine with allowing PING frames in Initial and Handshake, I wonder what the benefit is. IIUC, the reason of sending PINGs in case of PTO is to see if a previously sent ack-eliciting packet has reached the peer. That is determined by the peer sending an ACK. However, in case of Initial and Handshake packets, the only case when PTO fires and you do not have any data to send is when the endpoints are ready to switch to the next epoch. To rephrase, I think there might be a high chance of the endpoint not receiving an ACK due to the peer dropping the Initial keys (or the Handshake keys). Considering that, I am not sure if it is a good advice to suggest endpoints send PINGs on PTO. Rather, I think it might be worthwhile to suggest that when discarding a packet number space, unconfirmed loss does not affect the CWND.
Regarding my comment above, NAME points out that there is no such issue, as because the loss would never be confirmed in such case (due to the peer never sending an ACK). I agree.
Yes, that seems right. The packets you send in the old packet number space might as well have never happened once you discard state for that space.
The case that I found interop was that my client and one packet's worth of unacknowledged data. In the 1-RTT case, I'd just send the data in the first probe and then a PING in the second probe. Since I can't do that for Handshake, I have two unattractive options: send only one probe, reducing the probability of success introduce special logic to spread out the data into two packets with a CRYPTO frame (gross)
NAME : So, you are in favor of sending PINGs in the handshake, right?
NAME IIUC, CRYPTO(off=0,len=0) can be used as a replacement for PING. There is no need to have special logic to spread out the data. So it's not as gross as it seems. That said, considering the fact that we have PING in 1-RTT even though CRYPTO(off=0,len=0) can be used instead in 1-RTT also, I do not think there is a reason to argue against having PINGs for Initial and Handshake too.
NAME It is the most straightforward way to send PTO and I see no downside, so yes. That said, I know that many of our mechanisms (e.g. discarding keys) bake in assumptions about the handshake and who sends what and when, so maybe we should examine the implications of, for example, a client sending a Handshake PING before it gets any Handshake packets from the server.
When there is nothing to send today, the text says you can send a PADDING only packet. These packets will be removed from flight when Initial keys are dropped, but it may never be acknowledged, which loses some congestion control signal, including RTT measurements. For PTO, we decided you always send a PING if PTO expires and there's nothing to send. It seems like we should say the same for the handshake case now that it's part of PTO? This came from Ryan's comment on
Not sure that this helps all that much. Even though you might be eliciting an acknowledgment, you will still lose the packet when you discard keys, which could still happen. All you have done is increase the work that the recipient needs to perform.
It doesn't help that much, but it's a lot more consistent with the rest of the PTO design, so I think it's preferable.
In discussing our PTO strategy yesterday I realized that you only send a packet if you have something ack- eliciting outstanding. Would you not want to send that again?
Sometimes you can't send that again once the handshake is confirmed(there's an example in the text, but the easiest is something unreliable like DATAGRAM). Prior to handshake confirmation, the client needs to drive the handshake to confirmation so it may have to send something, as your PR discusses
Makes sense to me. I hadn't considered data that isn't retransmitted after loss, which is a little embarrassing.
This seems reasonable. The RTT estimate could be quite wrong at this stage in the connection, so an ack-eliciting packet can be used to improve that as well.
This looks good.Looks good to me, for what it's worth our implementation always bundles a PING in here if there is nothing else ack-eliciting to send.

client does not yet have 1-RTT keys.  If the probe timer expires before the client has 1-RTT keys, it is possible that the client may not have any crypto data to retransmit.  However, the client MUST  <ins> send a new packet to allow the server to continue sending data.  At this stage in the connection, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to improve its RTT estimate, the new packet that it sends MUST be ack- eliciting.  If Handshake keys are available to the client, it MUST send a Handshake packet, and otherwise it MUST send an Initial packet in a UDP datagram of at least 1200 bytes. Initial packets and Handshake packets may never be acknowledged, but they are removed from bytes in flight when the Initial and Handshake keys are discarded. </ins> 6.3.1.


6.2. <del> A Probe Timeout (PTO) triggers sending one or two probe packets when ack-eliciting packets are not acknowledged within the expected period of time or the handshake has not been completed.  A PTO enables a connection to recover from loss of tail packets or acks.  The PTO algorithm used in QUIC implements the reliability functions of Tail Loss Probe TLP RACK, RTO RFC5681 and F-RTO algorithms for TCP RFC5682, and the timeout computation is based on TCP's retransmission timeout period RFC6298. </del>  6.2.1. When a PTO timer expires, a sender MUST send at least one ack- eliciting packet as a probe, unless there is no data available to <del> send.  An endpoint MAY send up to two ack-eliciting packets, to avoid an expensive consecutive PTO expiration due to a single packet loss. </del>  It is possible that the sender has no new or previously-sent data to send.  As an example, consider the following sequence of events: new

Now that the PTO retransmits Initial and Handshake data, it's possible and likely desirable to bundle multiple QUIC packets in a single datagram. This change makes it clear that is allowed.
nits, but lgtm

6.2.  <ins> A Probe Timeout (PTO) triggers sending one or two probe datagrams when ack-eliciting packets are not acknowledged within the expected period of time or the handshake has not been completed.  A PTO enables a connection to recover from loss of tail packets or acknowledgements.  The PTO algorithm used in QUIC implements the reliability functions of Tail Loss Probe TLP RACK, RTO RFC5681 and F-RTO algorithms for TCP RFC5682, and the timeout computation is based on TCP's retransmission timeout period RFC6298. </ins> 6.2.1. When a PTO timer expires, a sender MUST send at least one ack- eliciting packet as a probe, unless there is no data available to  <ins> send.  An endpoint MAY send up to two full-sized datagrams containing ack-eliciting packets, to avoid an expensive consecutive PTO expiration due to a single lost datagram. </ins> It is possible that the sender has no new or previously-sent data to send.  As an example, consider the following sequence of events: new


On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip <del> time estimator for the new path. </del>  An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is

This doesn't address both editorial clarifications in , but the second one is now getting covered in .
This is awkward, because the issue of assumptions about the path appears spread over multiple places. Section 9.4 of draft-ietf-quic-transport : The capacity available on the new path might not be the same as the old path. Packets sent on the old path SHOULD NOT contribute to congestion control or RTT estimation for the new path. GF: That would be fine IF the new path were a separate CC context, which I am not yet clear whether it is, read on: — Section 9.4 of draft-ietf-quic-transport : On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip time estimator for the new path. GF: Why not MUST? - because I think this is a new path, I think it can only be should if something can confirm this is the same set of network-devices and classifiers on the path. I don't know how you would know that, and I don't like the guess. — draft-ietf-quic-transport : For instance, a change in the client's port number is likely indicative of a rebinding in a middlebox and not a complete change in path. GF: So. maybe this is the guess, and I don't believe it! In a world of ECMP and other methods, this needs to be treated as a potential change of path. — Section 9.4 of draft-ietf-quic-transport: While multiple paths might be used during connection migration, a single congestion control context and a single loss recovery context (as described in [QUIC-RECOVERY]) may be adequate. GF: Well it could if the paths WERE known to be identical, but how would an endpoint guarantee that? Section 9.2 of QUIC transport seems to suggest the CC is reset for the new path, which I would agree with, but which part of QUIC recovery says that? — draft-ietf-quic-recovery: For instance, an endpoint might delay switching to a new congestion control context until it is confirmed that an old path is no longer needed (such as the case in Section 9.3.3). GF: This seems underspecified and novel to me. I think it may b quite OK to continue using the old path and the old CC, for a while. Same question: is this implying the two paths share a single CC and is there some real mechanism to detect that they are really the same set of network devices on path?
1) SHOULD NOT -> MUST NOT Suggestion: "New paths have new congestion control contexts, implementations can reset the existing congestion control context." 2) In keeping with the first resolution, it's a new CC, and whether a new CC is created or the existing one is reset is an implementation decision. 3) Instead of saying it's the same path, say that the path parameters(RTT and CC state) are likely to be similar. 4) This is resolved with the resolution to 1, we can decide where the implementation note should live. 5) (In Section 9.4 of transport) This is really "A path can be validated, but not switched to immediately, particularly in the presence of a potential attack. When you switch, you need to use a new(or reset) congestion controller."
For 5, we need to say a bit more about the fact that the attack is that when a sender receives data on a new path causing it to potentially reset its CC state immediately if it switches immediately.
For 1,2,4: I think this was always the intent -- some implementations don't want to have two CC contexts so they can reset it and start with zero assumptions about the new path. Implementations that are okay with having more than one will make a new one and use that on the new path, switching back to the old one if they use the old path and the information is still relevant (i.e. it's been <1 RTT and you decided the new path wasn't actually better -- pick some threshold I don't have a strong preference). 3: Yes, it's not necessarily exactly the same path. An endpoint that wants to put in extra effort in determining which paths are equivalent to make a smarter decision can do so while the default behavior is a complete reset every time (after confirming the migration). This also impacts (5) around some of the attacks. 5: I agree with NAME we should be a bit more explicit about preventing an off-path attacker from being able to cause continuous resets as you go back and forth between paths, that's a good case where you would want to keep CC state around (if you're being told to swap multiple times per-RTT you've got other issues, too, which are mostly addressed but might need some more text, likely in transport).
Discussed in Cupertino. NAME to propose a PR, current belief is that it will be Editorial (classifying as such); if not, we'll reclassify.
We've summarized some discussion for these above, but to go through the resolution plan for concrete changes for each one: The intent here was that either (a) you only ever have a singe CC context and you reset it for each path, or (b) you have a CC context for each path, in which case each context is strongly tied to a path and you don't need to reset anything. We require that everyone does at least (a), while not preventing folks who want to do extra work for a potential performance gain from doing (b). Addressing this in a PR: They can contribute to the CC/RTT estimation for the old path if it's still around and you're willing to maintain that state, which is why this (mistakenly) was a SHOULD. Yes, this should be a MUST, because the CC/RTT for that path has to be reset for a new address, either by making a new one for the new path and keeping the old one around for the old path, or by resetting your single one. This is now a MUST in the current editor's copy. A very good point! This is now: This seems to address the need for caution here. This is intended to say that you aren't required to maintain additional per-path state, it's adequate to reset it for each path. In recovery, the section entitled "Handshakes and New Paths" discusses initial values to use on a new path. In general, recovery seems to be an instance of CC/LR, so if transport instructed you to toss out the old one and make a new one, you'd be using initial values, etc. as specified in recovery. Transport does require that you reset CC/RTT a few paragraphs above (as noted in your previous comment): I don't think we're trying to specify a new mechanism to determine equality of paths across a network. The intent here is to recommend that an endpoint be a little bit cautious about throwing out its state before it knows it's really going to be sticking with the new path.
I think a normal transport assumption is that a change of path implies a (potential) loss of congestion state, and that the endpoint should reset the path CC parameters. Transport-ID: “On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip time estimator for the new path.” • Does the "reset" require setting the CC to the initial window value for a connection? I think so. • Why not MUST? Transport-ID: “An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is valid for the new path. “ This could therefore be seen as a “significant” departure from conventional approaches considered safe - and extends this to “reasonably sure”. • Is there a rationale for extending this to “reasonably sure”, because this seems bolder than permitted for a new TCP connection. How do people quantify reasonably sure, and why not just in this case grow from the Initial window?
Yes. We should articulate this better though. To allow for pre-existing knowledge, such as stored values. I think this can be changed to say "unless an endpoint has other knowledge, it MUST reset" (or some such). Fair point. The intent here again was to allow pre-existing knowledge to be used, but I think the wording can be changed to reflect that more explicitly.
Discussed in London: NAME to provide a PR
Re-opening issue. This needs a bit more work.

On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip  <ins> time estimator for the new path to initial values (see Sections A.3 and B.3 in QUIC-RECOVERY). </ins> An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is


old path.  Packets sent on the old path SHOULD NOT contribute to congestion control or RTT estimation for the new path. <del> On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip time estimator for the new path to initial values (see Sections A.3 and B.3 in QUIC-RECOVERY). An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is valid for the new path.  For instance, a change in the client's port number is likely indicative of a rebinding in a middlebox and not a complete change in path.  This determination likely depends on heuristics, which could be imperfect; if the new path capacity is significantly reduced, ultimately this relies on the congestion controller responding to congestion signals and reducing send rates appropriately. </del>  There may be apparent reordering at the receiver when an endpoint sends data and probes from/to multiple addresses during the migration

Cleans up some text in the transport doc around reseting (or not) CC and RTT values on migration.
I think a normal transport assumption is that a change of path implies a (potential) loss of congestion state, and that the endpoint should reset the path CC parameters. Transport-ID: “On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip time estimator for the new path.” • Does the "reset" require setting the CC to the initial window value for a connection? I think so. • Why not MUST? Transport-ID: “An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is valid for the new path. “ This could therefore be seen as a “significant” departure from conventional approaches considered safe - and extends this to “reasonably sure”. • Is there a rationale for extending this to “reasonably sure”, because this seems bolder than permitted for a new TCP connection. How do people quantify reasonably sure, and why not just in this case grow from the Initial window?
Yes. We should articulate this better though. To allow for pre-existing knowledge, such as stored values. I think this can be changed to say "unless an endpoint has other knowledge, it MUST reset" (or some such). Fair point. The intent here again was to allow pre-existing knowledge to be used, but I think the wording can be changed to reflect that more explicitly.
Discussed in London: NAME to provide a PR
Re-opening issue. This needs a bit more work.
The associated issue is marked design, but this looks fine to me.Thanks, LGTMLGTM, "MUST, unless" seems like a nicer balance than the previous "SHOULD".

old path.  Packets sent on the old path SHOULD NOT contribute to congestion control or RTT estimation for the new path.  <ins> On confirming a peer's ownership of its new address, an endpoint MUST immediately reset the congestion controller and round-trip time estimator for the new path to initial values (see Sections A.3 and B.3 in QUIC-RECOVERY) unless it has knowledge that a previous send rate or round-trip time estimate is valid for the new path.  For instance, an endpoint might infer that a change in only the client's port number is indicative of a NAT rebinding, meaning that the new path is likely to have similar bandwidth and round-trip time. However, this determination will be imperfect.  If the determination is incorrect, the congestion controller and the RTT estimator are expected to adapt to the new path.  Generally, implementations are advised to be cautious when using previous values on a new path. </ins> There may be apparent reordering at the receiver when an endpoint sends data and probes from/to multiple addresses during the migration


1-RTT keys have forward secrecy <del> authenticated values for the transport parameters of the peer (see transport-parameters) </del>  authenticated negotiation of an application protocol (TLS uses ALPN RFC7301 for this purpose)

The TPs might carry a stateless reset token, which must be encrypted in transit. Section 7 in the transport document [1] lists some requirements for the handshake, including authentication of the TPs, yet does not include any mention of encrypting them from server to client. Should it? cc NAME NAME [1] URL
This makes sense to me. The TLS document ensures that the server's transport parameters are encrypted so an editorial PR changing the transport doc to require that should be sufficient.
Good catch. Marking as design so that we can consensus call this, but it's arguably editorial.

1-RTT keys have forward secrecy  <ins> authenticated values for transport parameters of both endpoints, and confidentiality protection for server transport parameters (see transport-parameters) </ins> authenticated negotiation of an application protocol (TLS uses ALPN RFC7301 for this purpose)


Stateless Reset Token. To entities other than its intended recipient, a stateless reset will <del> appear to be a packet with a short header.  For the packet to appear as valid, the Unpredictable Bits field needs to include at least 198 bits of data (or 25 bytes, less the two fixed bits).  This is intended to allow for a Destination Connection ID of the maximum length permitted, with a minimal packet number, and payload.  The Stateless Reset Token corresponds to the minimum expansion of the </del>  packet protection AEAD.  More unpredictable bytes might be necessary if the endpoint could have negotiated a packet protection scheme with a larger minimum AEAD expansion. close connections when other peers or connections have exhausted limits. <del> Reducing the size of a Stateless Reset below the recommended minimum size of 41 bytes could mean that the packet could reveal to an observer that it is a Stateless Reset.  Conversely, refusing to send a Stateless Reset in response to a small packet might result in Stateless Reset not being useful in detecting cases of broken connections where only very small packets are sent; such failures might only be detected by other means, such as timers. An endpoint can increase the odds that a packet will trigger a Stateless Reset if it cannot be processed by padding it to at least 42 bytes. </del>  11.

Allows smaller stateless resets to be sent(as small as 21 bytes) and advises endpoints that want to ensure a stateless reset is not detectable send a larger packet if their chosen CIDs are more than 2 bytes.
I don't understand what this is trying to specify. The stateless reset sender has no idea what CID length the peer has chosen (except for the 20 bytes limit), so I don't see how to guarantee that the Reset exceeds minimum size without allowing for the full 20 bytes. Unless this PR is trying to say that it's OK to go smaller if you're willing to risk it...
It's pretty much "it's OK to go smaller if you are willing to risk stateless reset messages being identifiable", but a little stronger than that. The sender of a stateless reset is now no longer bound by the upper limit on connection ID size. That allows for a smaller packet to be sent and still have it get a stateless reset in response. However, the onus for ensuring that the resulting stateless reset is indistinguishable from regular packets moves to the endpoint that would receive the stateless reset. It has to ensure that it never sends a packet small enough that a stateless reset packet would be too small to appear as valid. For instance, take the case where a client needs at least 8 bytes of connection ID, but the server had no connection ID. Though the client has no real need to send larger packets toward the server, it has to pad outbound packets so that the server, should it be forced to send a stateless reset, wouldn't end up sending a packet that appears to have a 7 byte connection ID (which, assuming an observer knows about the client's constraints, would be easy to identify as a stateless reset).
I find this text to be really unclear. Furthermore, if implementers don't pick up the SHOULD we're going to have detectable stateless resets all over the place. And given the final version of extended connection IDs, we're talking about 2 additional bytes over the draft-20 status quo? I don't find this change to be necessary, though I wouldn't raise a huge stink about it. However, I'd like to consider changing SHOULD to MUST. This seems like a potential security vulnerability. At an absolute minimum, I'll propose an editorial rewrite to make these considerations clearer.
In coming back to this, I can agree with Martin. This is very unclear.
Currently, the spec recommends sending Stateless Reset packets of at least 39 bytes. The number is the minimum packet size when using the maximum connection ID length of 18: type byte: 1 client connection ID: 18 packet number + payload: 4 auth tag: 16 39 = 1 + 18 + 4 + 16. The intention there was to make Stateless Resets harder to distinguish from regular server-to-client traffic on a given QUIC connection. However, when client connection IDs are in use, Stateless Resets are inherently distinguishable from regular traffic because they do not carry the previously-used client connection ID. One might say that they could be confused for a change in connection ID, which is true. But let's assume we just changed from an 18 byte client connection ID to a 4-byte connection ID (which is specifically allowed by the spec), then that new packet is still equally indistinguishable from a Stateless Reset. It is true that a client connection ID length change from 18 to 4 is unlikely, but that's not a problem. We just need it to be possible for this to be a valid packet to observers without keys. So I propose that instead of having a minimum required size for stateless resets of 21 and a recommended minimum of 39, we can just say the minimum required size is 25. The important value here is the minimum non-zero connection ID length, not the maximum connection ID length. At the end of the day, a Stateless Reset will stick out to observers who will notice that the client stops sending on that particular connection. The current value of 39 is harming the protocol by increasing the odds of servers not sending Stateless Resets which will cause timeouts on clients instead of failing fast. This is a tradeoff, and we should strive to avoid timeouts instead of trying to hide a packet that can't be hidden.
This is related to Issue
This does not permit the attacker any knowledge of the client. If a client always selects an 18 byte connection ID, a stateless reset will stand out. The goal is to avoid having the message interfered with in transit. After it gets through and is processed, what happens after doesn't matter.
is also related I believe.
The goal is not to avoid the attacker dropping the packet(I'm not sure what other type of interference is possible here?). If they can do that, they can drop all packets and that's likely even worse. The goal is to ensure the packet can't be reliably identified as a stateless reset, which is a much lower bar. As I stated on , if the client wants to ensure it's reset is always large enough to be indistinguishable, it needs to send a packet size that's one byte larger than the smallest packet it expects to receive. Typically, this is a no-op for the client, so compliance is trivial. No matter what happens, a server may send a stateless reset in response to any QUIC packet that's large enough to be valid, and it's not sensible to try to prevent a server from doing that. Even if the packet is smaller than other packets, it's just odd, not invalid, as David said. If we don't do this, I fear clients will increase their effective minimum packet size substantially, which is a cost everyone has to bear, when a large number of clients will use 0 byte CIDs.
My point is that the packet is invalid, if it is sent to a client that you know uses longer connection IDs. The middlebox can then reliably identify the packet as being a stateless reset. All it needs to know is that that client never uses shorter connection IDs. That's not difficult; we don't assume that this sort of information is a secret.
The only rule is that the CID can't become 0 length, so why is it invalid? If the middlebox can't reliably identify the packet as invalid, it's all a matter of statistics and how much of an anomaly it is. Sure, maybe it's an anomaly for CIDs to go from 18 to 1, but I don't think that's the most likely anomaly on the internet(see below). I think the existing rule is actively harmful in multiple ways, one of which is a possible escalation of the minimum client packet size mentioned above. The more problematic issue is that the goal is to have stateless reset not stick out and be clearly identifiable. If so, we want the widest selection of sizes possible. Given servers are going to want to send stateless resets smaller than the client's packet, a fixed size is going to stick out and the larger the minimum fixed size, the more it's going to stick out. In particular, 40 is enough larger than the typical 'small' server to client packet(ie: ACK) that it's relatively unlikely on a typical connection. If 90%+ of 40 byte payloads are stateless rejects, then we have failed at our goal and I think that's where we're at today. To come back to my previous suggestion, why can't the client enforce this?
It's not generically invalid, it's only invalid in a particular context. And we have to assume that our adversary understands that context better than the endpoint that generates the stateless reset. That's all. To be clear, we don't expressly forbid sending of a smaller stateless reset, only strongly imply that it won't be effective. So I'm struggling to understand what you would concretely do here.
NAME What is this adversary trying to accomplish here? Can you elaborate on threat vector? The number 39 is reducing the reliability of the protocol, what security property does it give us in exchange?
I believe that we wanted to avoid the observer from being able to recognize resets. The idea being that recognition preceded some sort of action. The worst I can imagine here is dropping state prior to the endpoints being ready for that. For instance, forwarding the packet and then dropping NAT bindings immediately, which - if that packet is lost - might leave the recipient with the belief that it still has an active connection when it does not. The point being that we're attempting to avoid facilitation of bad behaviour. Perhaps you can talk about the way in which you perceive reliability being reduced. 39 isn't exactly a very large number, nor is it a hard limit.
I agree with this threat model. We really want to avoid NATs reacting to stateless resets the way they react to TCP RSTs today. However, we've heard from implementors that the majority of QUIC traffic will not use client connection IDs. But there will be an implementation that uses them (either all the time, or at least some of the time, to apply grease to servers to make sure they support them). Let's assume there is a client implementation called CLIENT18 that always uses 18-byte client CIDs. Do you think someone will go out of their way to implement code that detects CLIENT18 traffic, and then installs a rule for those connections only where the detection of a server-to-client packet shorter than 39 bytes will drop the NAT mapping? This sounds about as likely as a NAT implementation that drops packets whose connection IDs are prime numbers... To answer your question on reliability: I think it will be common to have a client establish a connection, send some requests, get the responses, then idle for a few minutes, then after user action, sends another request. Assuming the server uses 8-byte CIDs, this will cause the client to send a packet than can easily be under 39 bytes (assuming a short request). Let's assume the server has restarted during the idle period. It won't recognize the connection ID. If the server decides that this packet is too short to trigger a stateless reset, then the client will timeout causing user-visible delays. For a protocol with such a goal of improving latency, a timeout is a critical failure. The issue here is that the spec doesn't mandate the client to pad its packets, and allows servers to not respond to short packets. Giving both implementations too much leeway is dangerous here. It would be in the spirit of the spec to be more prescriptive. We remove the 39-byte requirement, and tell servers that their stateless resets should be of length in the range . We then tell clients to pad their packets such that .
I could live with that. That is a lot harder to write down, but I think that it might work. I wouldn't make the calculation so complex (just say that the minimum packet size is to account for 1 type byte, 1 data byte, and 16 auth tag bytes plus the connection ID length and the one extra byte we lose for the stateless reset) and I wouldn't tie it to a particular role (stateless reset is supposed to be symmetric, it's just that one direction in particular is more likely by far). We can leave the details to implementations. I was concerned about clients that were also identifiable by means easier than fingerprinting. Say someone who deployed client code in their server cluster, where connection IDs are far more likely as a client, for whom any packet (to a client or server) could be readily identified as a stateless reset by virtue of its size. Profiling by IP address would be easy for someone to do in that case.
What about small ACK-only packets? Would they have to be padded - and what would the avg. cost be of that?
I also agree that not recognizing resets is the goal, but I don't think we're currently achieving that for reasons stated above. And as David pointed out, I think there are allowed behaviors that would not elicit a stateless reset when you'd really like to have one sent. Why maxcid length, not mincid length if we're trying to establish the lower bound on the packet size that could have been sent previously in the connection? Either way, mincid+19 works for me, since I'm mostly concerned about cases with 0, or small fixed CID lengths.
You are right, it's the minimum length. For 0, that means 0.
One calculation error to fix, and some editorial suggestions.

Stateless Reset Token. To entities other than its intended recipient, a stateless reset will  <ins> appear to be a packet with a short header.  For the stateless reset to appear as a valid QUIC packet and be smaller than the received packet, the Unpredictable Bits field needs to include at least 46 bits of data (or 6 bytes, less the two fixed bits).  To ensure the stateless reset packet is not smaller than other packets received on the connection, an endpoint SHOULD also ensure the total packet length is at least the minimum chosen CID length plus 22 bytes.  22 bytes allows for 1 type byte, 4 packet number and data bytes, 16 bytes for AEAD expansion, and an extra byte to allow the peer to send a smaller stateless reset than the packet it receives. The Stateless Reset Token corresponds to the minimum expansion of the </ins> packet protection AEAD.  More unpredictable bytes might be necessary if the endpoint could have negotiated a packet protection scheme with a larger minimum AEAD expansion. close connections when other peers or connections have exhausted limits.  <ins> Reducing the size of a Stateless Reset below 41 bytes means that the packet could reveal to an observer that it is a Stateless Reset, depending upon the length of the peer's connection IDs.  Conversely, refusing to send a Stateless Reset in response to a small packet might result in Stateless Reset not being useful in detecting cases of broken connections where only very small packets are sent; such failures might only be detected by other means, such as timers. </ins> 11.


by the endpoint upon receipt. Connection IDs MUST NOT contain any information that can be used by <del> an external observer to correlate them with other connection IDs for the same connection.  As a trivial example, this means the same connection ID MUST NOT be issued more than once on the same connection. </del>  Packets with long headers include Source Connection ID and Destination Connection ID fields.  These fields are used to set the

S 5.1. says At minimum this needs to be restricted to endpoints and other elements associated with them (like load balancers) because they need to be able to correlate connection IDs. But this is also a very strong condition, and in some cases will basically mandate encryption.
It should contain "by an external observer". I believe there is other text mentioning coordination with load balancers when constructing CID's. It is a strong condition - without it all other work on preventing linkage is more or less useless.
It feels strange to me that this is an RFC8174 MUST NOT, as it's unclear how to enforce it. That said, I agree that this text should be constrained to external observers - and clearly define who that is.
Maybe change to “Connection IDs MUST NOT expose”?
Tokyo conclusion: describe what the attack is and explain that anti-correlation text exists as an answer to this. The normative language is perhaps too strong, given that it might be possible to create smaller anonymity sets and still have a reasonable answer to the attack.
Discussed in London, NAME to resume work
NAME how is this progressing?
I believe this is an editorial issue, not design. We all know what we mean: it can encode information by use for itself and trusted intermediaries, but definitely is not correlatable. I agree that MUST is a little tricky unless we are mandating either true encryption or true randomness, which is a discussion I was hoping to defer until QUIC-LB is adopted.
Looks fine. This paragraph is wrapped to a shorter line-length than the standard for the document.

by the endpoint upon receipt. Connection IDs MUST NOT contain any information that can be used by  <ins> an external observer (that is, one that does not cooperate with the issuer) to correlate them with other connection IDs for the same connection.  As a trivial example, this means the same connection ID MUST NOT be issued more than once on the same connection. </ins> Packets with long headers include Source Connection ID and Destination Connection ID fields.  These fields are used to set the


greater than its own Insert Count, the stream cannot be processed immediately, and is considered "blocked" (see {blocked-decoding}). <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 become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all times.  Note that the decoder might not become blocked on every stream which risks becoming blocked. </del>  An encoder can decide whether to risk having a stream become blocked. If permitted by the value of SETTINGS_QPACK_BLOCKED_STREAMS, it MAY treat this as a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED. <del> If the decoder encounters more blocked streams than it promised to support, it MUST treat this as a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED. </del> 2.2.2. The decoder signals the following events by emitting decoder

Emphasize that SETTINGSQPACKBLOCKED_STREAMS is set by the decoder. Move normative sentence on decoder encountering excessive number of blocked streams to this paragraph. Split note to separate paragraph.
:+1: This reads much better to me.

greater than its own Insert Count, the stream cannot be processed immediately, and is considered "blocked" (see {blocked-decoding}).  <ins> The decoder specifies an upper bound on the number of streams which can be blocked using the SETTINGS_QPACK_BLOCKED_STREAMS setting (see configuration).  An encoder MUST limit the number of streams which could become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all times.  If an decoder encounters more blocked streams than it promised to support, it MUST treat this as a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED. Note that the decoder might not become blocked on every stream which risks becoming blocked. </ins> An encoder can decide whether to risk having a stream become blocked. If permitted by the value of SETTINGS_QPACK_BLOCKED_STREAMS, it MAY treat this as a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED.  2.2.2. The decoder signals the following events by emitting decoder


The Header Acknowledgement and Stream Cancellation instructions permit the encoder to remove references to entries in the dynamic <del> table.  When entries have zero references they are no longer considered blocking (see blocked-insertion). </del>  2.2.2.3.

The definition of a blocking entry has two parts: insertion not acknowledged, or not all references acknowledged. Update text in two places to include the part about acknowledging the insertion, lest a reader forget about it.

The Header Acknowledgement and Stream Cancellation instructions permit the encoder to remove references to entries in the dynamic  <ins> table.  When an entry with absolute index lower than the Known Received Count has zero references, then it is no longer considered blocking (see blocked-insertion). </ins> 2.2.2.3.


2.2.2.1. <del> When the decoder finishes decoding a header block containing dynamic </del>  table references, it MUST emit a Header Acknowledgement instruction (header-acknowledgement).  A stream may carry multiple header blocks in the case of intermediate responses, trailers, and pushed requests.

Add MUST to sentence about requirement on decoder to emit a Header Acknowledgement instruction every time it decodes a header block with non-zero Required Insert Count.
That's a good point, I haven't thought about it. Would changing "When the decoder finishes decoding..." to "After the decoder finishes decoding..." be sufficient to allow for delayed ACKs?
Works for me.
Done. Thanks for raising this issue.
You can't coalesce Header Acks like you can Insert Count Increment or transport Acks. The only overhead savings comes from when these are sent to the peer - eg: transport overhead. The text only says the Ack, and doesn't really describe how quickly it has to be conveyed. But I don't really have a problem with when vs. after either.
This doesn't allow for a delay in sending an ack. That delay might be useful in reducing overheads. That might be a new issue though.

2.2.2.1.  <ins> After the decoder finishes decoding a header block containing dynamic </ins> table references, it MUST emit a Header Acknowledgement instruction (header-acknowledgement).  A stream may carry multiple header blocks in the case of intermediate responses, trailers, and pushed requests.


A unique index for each entry in the dynamic table. <del> A reference point for relative indices.  Dynamic references are made relative to a Base in header blocks. </del>  The total number of entries inserted in the dynamic table. For example, with a Required Insert Count of 9, a decoder receives a S bit of 1 and a Delta Base of 2.  This sets the Base to 6 and enables post-base indexing for three entries.  In this example, a <del> regular index of 1 refers to the 5th entry that was added to the </del>  table; a post-base index of 1 refers to the 8th entry. 4.5.2.

Since post-base index has a section separate from relative index, I do not consider post-base index a type of relative index, but a distinct category. Update text accordingly. If the consensus is that this is not the case, relative index and post-base index sections should be reworded, and a new term should be introduced for non-post-base relative index.

A unique index for each entry in the dynamic table.  <ins> A reference point for relative and post-base indices.  References to dynamic table entries in header blocks are relative to a Base. </ins> The total number of entries inserted in the dynamic table. For example, with a Required Insert Count of 9, a decoder receives a S bit of 1 and a Delta Base of 2.  This sets the Base to 6 and enables post-base indexing for three entries.  In this example, a  <ins> relative index of 1 refers to the 5th entry that was added to the </ins> table; a post-base index of 1 refers to the 8th entry. 4.5.2.


4.1. As shown in schematic, the interface from QUIC to TLS consists of <del> three primary functions: </del>  Sending and receiving handshake messages  Rekeying (both transmit and receive) Handshake state updates 4.7.  In TLS over TCP, the HelloRetryRequest feature (see Section 4.1.4 of TLS13) can be used to correct a client's incorrect KeyShare extension as well as for a stateless round-trip check.  From the perspective of instead use the Retry feature (see Section 8.1 of QUIC-TRANSPORT). HelloRetryRequest is still used to request key shares. <del> 4.8. </del>  If TLS experiences an error, it generates an appropriate alert as defined in Section 6 of TLS13. The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level. <del> 4.9. </del>  After QUIC moves to a new encryption level, packet protection keys for previous encryption levels can be discarded.  This occurs several and retransmissions of data in CRYPTO frames are sent at a previous encryption level.  These packets MAY also include PADDING frames. <del> 4.9.1. </del>  Packets protected with Initial secrets (initial-secrets) are not authenticated, meaning that an attacker could spoof packets with the This results in abandoning loss recovery state for the Initial encryption level and ignoring any outstanding Initial packets. <del> 4.9.2. </del>  An endpoint MUST NOT discard its handshake keys until the TLS handshake is confirmed (handshake-confirmed).  An endpoint SHOULD eliciting frames, such as PING, which will cause the handshake to be confirmed when they are acknowledged. <del> 4.9.3. </del>  0-RTT and 1-RTT packets share the same packet number space, and clients do not send 0-RTT packets after sending a 1-RTT packet

This PR describes how a TLS stack needs to cooperate with a QUIC stack when making the decision whether or not to accept early data. The purpose of this change is so that implementors working on the TLS layer of QUIC are aware that other layers impact decisions traditionally made at the TLS layer.
This could potentially also update the diagram at URL (adding a line for the server between "Handshake Received" and "Get Handshake").
Do I need to take any action to get this PR merged? (Should this be labeled editorial?)
I think that this is borderline editorial, as you more or less have to do this. It does change the API, which is enough of a change that it's worth having an issue. I'll open an issue and ask the chairs to call consensus.
I opened a related issue -- . The conversation above focuses on the server, but what about ticket handling by the client? Current ticket stores are per SNI. Should they become per-SNI + per-ALPN?
Does it even make sense to assume ALPN is always present in multiplexing protocols? An endpoint could have multiple active protocols on the same connection even if that is not supported by any protocol we currently know of.
ALPN is required by QUIC.
This is borderline off topic - but if ALPN is visible and firewalls use this to block traffic, endpoints might want to hide the ALPN. I suppose there is some ongoing work on ALPN encryption?
In the new section 5.4. of the transport draft, "Required Operations on Connections", we see a requirement that When implementing the server role, applications need to be able to: o ... o if Early Data is supported, retrieve application-controlled data from the client's resumption ticket and enable rejecting Early Data based on that information. I read the corresponding text in the HTTP draft, and I understand that the idea is to remember the HTTP Settings when resuming an HTTP connection. That's fine, but it seems to assume that tickets are tied to a specific ALPN. Otherwise, if a server supports several applications over the same port, then the clients need to keep track of the ALPN used when the ticket was received. There is no such requirement in TLS 1.3 proper. In Section 4.6.1. of RFC 8446, "New Session Ticket Message", we can read that Clients MUST only resume if the new SNI value is valid for the server certificate presented in the original session and SHOULD only resume if the SNI value matches the one used in the original session. Notice that there is no mention of the ALPN. We can thus expect the ticket store to be indexed by SNI, plus also some consideration about certificate explained later in the text. There are of course lots of reason for a server to support multiple ALPN over a single port. For example, the server might support H3 and H3.1. Or it might want to provide access over port 443 for DNS over QUIC, IMAP over QUIC or other such applications. With TLS 1.3, they can do that, demux on the ALPN, and 0-RTT will work fine. But if we make the session tickets application specific, that won't work. Which is why I think the idea of putting HTTP settings in the session ticket is ill conceived.
IIUC, while it is possible to use a session ticket when the SNI is the same, you can only use early data when ALPN also matches. Quoting from RFC 8446 section 4.2.10:
I think that we can address this by saying that clients need to remember this state alongside any ticket. (And we should make it clear that the server needs to remember this also. It doesn't have to put things in the ticket, though that might make it easier for it to correctly remember.)
I believe a comment is needed in 5.4 to at least remind the reader that other ALPN's might receive the ticket and would not be able to make use of the settings data which therefore need coordination across token consumers.
NAME As I stated in URL, it is my understanding that early-data can be used only when the ALPN matches, and therefore a QUIC stack is required to consult the application-specific data in the ticket only when the ALPN matches. Therefore I do not see any need for cross-application-protocol coordination. Am I missing something?
NAME Assuming things work as you say and a client receives a token from ALPN-X, and assuming the client does not remember tokens per ALPN, then you can have another ALPN named ALPN-Y in another session receiving the token generated for ALPN-X. If the ALPN-Y endpoint does not understand how to at least ignore ALPN-X data in the token, you can get undefined behaviour.
NAME I do not think so. In the case you describe, the TLS stack will not indicate to the QUIC stack that 0-RTT is acceptable because the ALPN is different. Therefore, the QUIC stack handling ALPN-Y would not use the application-protocol-specific data in the session ticket (as it is expected to be used only when early data is acceptable - that's what section 5.4 of the transport draft says). Hence no confusion.
OK - I don't know enough about how TLS validates tokens - but if it does embed the ALPN in token validation, then the coordination I am asking for is already happening.
So this means that 0-RTT does not work across protocols? This is unfortunate because if a client connects for HTTP and then connects for WebRTC data, it cannot use 0-RTT for WebRTC.
Correct. That is the restriction of TLS 1.3.
So spec-wise, I think we might want to change: "if Early Data is supported, retrieve application-controlled data from the client’s resumption ticket and enable rejecting Early Data based on that information." to "if Early Data is supported, and when the TLS protocol stack indicates that Early Data can be accepted, retrieve application-controlled data from the client’s resumption ticket and enable rejecting Early Data based on that information." as well as suggesting that the application protocol-specific information is to be associated rather than stored, as pointed out by NAME in URL
I am not too worried about the server side. Servers that support multiple applications can put adequate information in the token, such as for example a server release number that points to the initial condition for multiple protocols. But I am concerned about clearly spelling out the client-side requirements, and I would rather see two parallel requirements: remember the initial conditions per ALPN+SNI, and remember the ticket per SNI. Note that we want fast rotation of tickets for privacy reasons -- use tickets at most once if possible. That works better if we don't add rigidity, such as "only reuse tickets per SNI+ALPN". I think that resuming across multiple ALPN is a valid scenario. Kazuho points out that it may not be well supported in TLS 1.3, but we don't want to further ossify the lack of support. Of course, none of that matters if we run all application protocols over HTTP.
I don't think it's "not well supported," it's entirely not permitted by TLS 1.3. You can resume a session, saving crypto work, but there won't be early data on it. If TLS changes in a future version or extension, QUIC can match. However, the most recent update to the text on how you handle stored SETTINGS does have a case for not being able to recover the SETTINGS from last time, which would seem to apply if TLS permitted early data from a non-HTTP session. You get the defaults, until you see the SETTINGS frame. The required capability could include not finding any corresponding data in the ticket.
PR describes a new piece of the interface between a QUIC and TLS implementation as it relates to 0-RTT. That's not really new - you had to do this to get 0-RTT working - but it is enough new text that it's worth having this pass under the collective noses of the working group. NAME has produced text, which I think is good, but we should run the process. Chairs, my suggestion is that we mark this "design" and run a consensus call.
Closed with .
The text looks like a nice improvement :+1: :+1:. LGTM modulo the point below.

4.1. As shown in schematic, the interface from QUIC to TLS consists of  <ins> four primary functions: </ins> Sending and receiving handshake messages <ins> Processing stored transport and application state from a resumed session and determining if it is valid to accept early data </ins> Rekeying (both transmit and receive) Handshake state updates 4.7. <ins> When a server receives a ClientHello with the "early_data" extension, it has to decide whether to accept or reject early data from the client.  Some of this decision is made by the TLS stack (e.g., checking that the cipher suite being resumed was included in the ClientHello; see Section 4.2.10 of TLS13).  Even when the TLS stack has no reason to reject early data, the QUIC stack or the application protocol using QUIC might reject early data because the configuration of the transport or application associated with the resumed session is not compatible with the server's current configuration. QUIC requires additional transport state to be associated with a 0-RTT session ticket.  One common way to implement this is using stateless session tickets and storing this state in the session ticket.  Application protocols that use QUIC might have similar requirements regarding associating or storing state.  This associated state is used for deciding whether early data must be rejected.  For example, HTTP/3 (QUIC-HTTP) settings determine how early data from the client is interpreted.  Other applications using QUIC could have different requirements for determining whether to accept or reject early data. 4.8. </ins> In TLS over TCP, the HelloRetryRequest feature (see Section 4.1.4 of TLS13) can be used to correct a client's incorrect KeyShare extension as well as for a stateless round-trip check.  From the perspective of instead use the Retry feature (see Section 8.1 of QUIC-TRANSPORT). HelloRetryRequest is still used to request key shares.  <ins> 4.9. </ins> If TLS experiences an error, it generates an appropriate alert as defined in Section 6 of TLS13. The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level.  <ins> 4.10. </ins> After QUIC moves to a new encryption level, packet protection keys for previous encryption levels can be discarded.  This occurs several and retransmissions of data in CRYPTO frames are sent at a previous encryption level.  These packets MAY also include PADDING frames.  <ins> 4.10.1. </ins> Packets protected with Initial secrets (initial-secrets) are not authenticated, meaning that an attacker could spoof packets with the This results in abandoning loss recovery state for the Initial encryption level and ignoring any outstanding Initial packets.  <ins> 4.10.2. </ins> An endpoint MUST NOT discard its handshake keys until the TLS handshake is confirmed (handshake-confirmed).  An endpoint SHOULD eliciting frames, such as PING, which will cause the handshake to be confirmed when they are acknowledged.  <ins> 4.10.3. </ins> 0-RTT and 1-RTT packets share the same packet number space, and clients do not send 0-RTT packets after sending a 1-RTT packet


Unlike TLS over TCP, QUIC applications which want to send data do not send it through TLS "application_data" records.  Rather, they send it <del> as QUIC STREAM frames which are then carried in QUIC packets. </del>  4.

Mention that application data could be conveyed in other frame types than STREAM frames. (E.g. the proposed DATAGRAM draft sends application data in DATAGRAM frames.)
This was correct at the time, because only STREAM carried application semantics, but that's too constraining, even now. Thanks.

Unlike TLS over TCP, QUIC applications which want to send data do not send it through TLS "application_data" records.  Rather, they send it  <ins> as QUIC STREAM frames or other frame types which are then carried in QUIC packets. </ins> 4.


8. QUIC uses the TLS handshake for more than just negotiation of <del> cryptographic parameters.  The TLS handshake validates protocol version selection, provides preliminary values for QUIC transport parameters, and allows a server to perform return routability checks on clients. </del>  8.1.

I'm pretty sure this was obsoleted when version negotiation was removed (along with initial/negotiated/supported versions from transport parameters). If you think the TLS handshake is still validating protocol version selection, feel free to reject this PR.

8. QUIC uses the TLS handshake for more than just negotiation of  <ins> cryptographic parameters.  The TLS handshake provides preliminary values for QUIC transport parameters and allows a server to perform return routability checks on clients. </ins> 8.1.


other packet number spaces with in-flight data, coalescing packets if possible.  When the PTO timer expires, and there is new or previously sent unacknowledged data, it MUST be sent.

Indicates that peers can skip a packet number when sending a single PTO packet to elicit a faster ACK.
This is a clever idea, but I don't think that we want to write it down in the spec. It's allowed by the spec, so people can surely do it, but we are talking about immediate ACK signaling in other ways too, and I don't think we want to encourage this behavior.
NAME Yeah, it's incredible how much money they will make by skipping packet numbers. If I had just known about this before...
I'm going to run an experiment comparing 1 PTO packet, 2 PTO packets, and 1 with skipping a packet number and report back the results. If the results are good enough with 1PTO + gap, we can consider whether this is an optimization worth mentioning.
Can also test this both sides at the same time? I'm still concerned this might run away in ACK floods.
Google and Chrome have been running experiments with the new IETF QUIC PTO algorithms post-handshake for a few months with Google QUIC, and it appears the first PTO is too conservative relative to Google QUIC. On the positive side, the rate of spurious TLPs/PTOs is MUCH lower, so that's WAI. This slows down detection of tail losses and causes a regression in 95%(and higher) latency metrics. It's also visible as an increase in rebuffers for YouTube. The changes aren't enormous, but they're large enough I can't launch this as is. I believe it's possible to make the first PTO slightly earlier, and stay aligned with the principles of TCP TLP and RTO. Specifically, my suggestion is to set first PTO expiry from the left edge(), not the right edge, but there's an additional safety measure of waiting 1.5 * SRTT since the last ACK-eliciting packet was sent(1.5 is from TLP). It'll be a few more weeks before we have data from Chrome Stable, but I believe this may be enough to remove the regression. Chromium code . One other note is we found that only sending one PTO packet seems sufficient as long as a packet number is skipped, so the delayed ack timer doesn't come into play. (Later added in PR ) NAME or others may also have production metrics relevant to this issue.
is entitled "IP Router Alert Considerations and Usage". Do you refer to (5.1) in ? That reads to me like a left edge condition, based on "timer is not running". Whereas : I can see one 1.5sRTT factor in , but that is inflated quite a bit, so I'm not sure exactly what you think motivates this 1.5 value. I do see the reason for your caution here. You want to probe based on the first unacknowledged packet, but leave a buffer relative to the last unacknowledged packet (assuming all elicit acknowledgment, of course). This is because all outstanding packets will all cause an acknowledgment if they arrive. So this seems sensible, if a little more aggressive than we have been. I don't know what the usual relationship between RTT and RTTvar is - I'm guessing that like everything it varies greatly. I'm inferring that the buffer you want is as much based on allowing for variance as much as anything else. The PTO is chosen to correspond roughly to 4-sigma, which is 99.994% and probably too conservative. That conservatism is probably what dominates your numbers. That also suggests that in the cases that drive your numbers, 4rttvar is greater than 0.5rtt. I'm guess that it is a LOT greater. Possible alternatives would be to cap rttvar to a proportion of the RTT estimate, maybe just for th purposes of calculating the PTO. 12.5% would get you almost exactly what you describe without changing the PTO calculation. It seems to me that PTO on 2-sigma and capping rttvar to RTT/4 is probably better. That's 2-sigma/95% confidence without the cap, and exactly your numbers if the cap has to be used. Either way, that recognizes that one probe timeout is primarily dependent on the estimated RTT and is less susceptible to variation in that estimate. I would like to request that you open a separate issue for the packet number skipping thing. That seems separable.
Yes, I meant RFC 6298, and I just updated the original post to reflect that. In terms of rttvar, the production data I'm seeing indicates rttvar is fairly close to 1/2 SRTT on average, which isn't so surprising given SRTT is an EWMA and for it to be on the order of SRTT, it would require estimates fairly close to 0 to balance out the higher estimates. Unfortunately, rttvar is RTT variation, not RTT variance(or RTT stdev). As such, I don't know if we can reason about what fraction of spurious PTOs would be within SRTT + 4RTTVar, even if we could assume a normal distribution(which is also likely wrong, but I think the least problematic assumption at the moment). I'd really rather not cap rttvar in any way, since I can't come up with any intellectual justification for that. The 1.5 constant from TLP does seem a bit arbitrary, but it is larger than SRTT. We added code in Chrome to try RTTstdev(EWMA'd), but that's even more experimental than the suggestion in my first post on this issue. I think there's a good chance 2RTTStdev is the best option(vs 4*RTTVar), but I don't have data to support that yet. I reopened PR for packet number skipping. I can open an issue if needed, but it's a simple non-normative PR.
Ah, I slipped into the same trap as usual. Mean absolute difference from the mean is the EWMA approximation we are using. Mean absolute difference is always less than standard deviation, with the factor being ~0.8 for a normal distribution. Do you have information on the distribution of RTT values that might help determine whether 2 is the right multiplier? If you have a normal distribution or something approximating that, then 3 would be closer (3.2 and change even). I can justify capping PTO, but it requires that you be comfortable with being more aggressive. But, at least to me, it seems like this is all about more aggressive sending. Which naturally improves performance most of the time. The network is likely to be able to absorb a minutely higher rate of probe packets as a result. Based on recent history of the RTT estimate, the peer can produce an acknowledgment within sRTT most of the time. Anything more than value is included only to allow more time for the peer to respond. However, if this is the trailing edge of a flight of packets, then any packet in that flight can be acknowledged. Allowing for some amount of variation in delays relative to the leading edge seems sensible, but delays on the trailing edge do not need any such justification. The only reason not to use the leading edge alone is that you don't want to send probes when you are sending productive data within the congestion window. On that basis, probing on the RTT estimate is fine. That means dead air of one RTT, and maybe some wasted probes because of jitter in the network that extends the effective round trip time. But you don't need to avoid unnecessary probing with very high confidence, you just need to avoid it most of the time. The risk with probing sooner is that RTT might genuinely increase, so that probes - which might exceed the congestion window - don't help at all. That should be rare; we have to treat past performance as a predictor for future gains, that's the basis of all of these systems. As long as there is an exponential backoff on PTO, we're unlikely to cause significant congestion as you only ever send a handful of probes before flipping to persistent congestion. A shorter and more aggressive timer here also results in hitting persistent congestion earlier. So if there really is a routing flap that increases RTT significantly - a case where you really want to reset the congestion window anyway - then you will just end up in hitting that state sooner. Another justification for a shorter PTO is that your estimation disadvantages shorter sequences of packets. A single packet flight doesn't get probed any sooner under your algorithm, but that is more likely to see loss of the entire flight than a longer sequence of packets.
Out of curiosity, is there any ongoing work using perceptrons or similar for predicting retransmission behaviour like it is being used in CPU branch prediction? URL URL
NAME Everything you said makes sense to me. I'm hoping to come up with a proposal that does 2 things: 1) Is not a regression for our current gQUIC implementation (or TCP best practices) 2) Build on well publicized and accepted concepts from TCP(ie: TLP and RTO) 3) Reduce the rate of spurious retransmits vs gQUIC. In terms of RTTVar vs an EWMA'd Stdev, I suspect the end result may be something like: SRTT+4RTTVar OR SRTT + 2(or 3)EWMA_Stdev, but I don't have enough data to know for sure yet. The experiments have been started, so hopefully I should know more in 2 weeks.
Instead of 1.5 SRTT we could consider SRTT + RTTVar. I think that's slightly more principled, but should be similar to 1.5 SRTT for typical connections.
I have experiments ongoing, but they're waiting on a Chrome rollout, so I'm happy with this being parked or otherwise not delaying WGLC.
I'll park this for now. Please propose to unpark once you have data.
I think this is worth exploring. We can also run an experiment with a subset of our traffic, so that we can be sure this is at least neutral for our current deployment. I don't think we should ignore a major deployment's signal, as this will inevitably lead to people shipping this (or some other mitigation) eventually as the de facto spec, similar to various TCPisms that are de facto standards.
If we want to have this impact QUICv1, we need data and a proposal in a relatively short timeframe. We're very close to being able to last-call the specs, and I don't want to delay that. So interested parties should prioritize this.
The most recent set of experiments indicate that my above suggestion of arming for max(leftedge + SRTT+4RTTVar, rightedge + 1.5SRTT) is a very slight improvement for some metrics, but it looks to be in the noise for most metrics. We did find SRTT+2RTTVar is as good or better than SRTT+4RTTVar for all application metrics, with a very small difference in retransmit rates(probably because PTO can send new data). I'd be very curious to see what NAME finds in his testing, but I'm increasingly inclined to close this with no action. Update: The above being said, we're going to default enable the version that arms from the left and right edges for the first PTO.
I will have data on this in a few days.
The editors discussed this and concluded that it would be best to mark this QUICv2.
Following up, I ran a test for 4 days on a major country. The results are mostly in line with what NAME saw. Tail response latency (p95) saw a measurable but small decrease of about 1 percent. Various application-level outlier metrics (e.g. time to load images, time to load a feed, etc.) showed slight improvements that also correspond with the change. There were no regressions in any other metrics. Based on this I think the strategy of arming the first PTO based on max(leftedge + SRTT+4RTTVar, rightedge + 1.5SRTT) is indeed an improvement, but it is not huge. I would agree with punting work on this to v2, since we plausibly will have time to come up with different strategies here that may be a larger improvement.
Thanks for the data NAME !
This probably shouldn't make me nervous, but it still does. But I still greatly appreciate the data. For my own peace of mind (though you will never alleviate the nervousness), can you confirm that you track both observed loss rates AND the rate of spurious retransmission? It strikes me that there would be high correlation between improvements in latency and real loss, so the loss rates probably don't change except where the faster transmission coincides with true congestion (which I would expect to be undetectable), and the same is likely also true for spurious retransmission, but I just wanted to confirm.
NAME For what it's worth, I did extensive testing of this change on smaller scale first, e.g. A/B tests on groups of individual hosts in different locales to ensure that nothing was fundamentally problematic. But indeed, there is good reason to be nervous when experimenting with recovery on the Internet One thing I'll note here is that the vast majority of the newly-fired PTOs in this test (i.e. PTOs that previously would have fired later) were not retransmissions at all but rather PTOs using fresh application data. As such the retransmission rate, spurious or otherwise, was pretty much constant when compared to the control. I think your intuition about the loss rate is totally warranted; in this test though there was not a statistically significant change in the observed loss rate, either the average or in the p95+ or p99 tail.
Marking as proposed and closing.

other packet number spaces with in-flight data, coalescing packets if possible. <ins> If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay. </ins> When the PTO timer expires, and there is new or previously sent unacknowledged data, it MUST be sent.


the peer sends an ACK immediately when receiving a second ack- eliciting packet. <del> In order to accelerate loss recovery and reduce timeouts, the receiver SHOULD send an immediate ACK after it receives an out-of- order packet.  It could send immediate ACKs for in-order packets for a period of time that SHOULD NOT exceed 1/8 RTT unless more out-of- order packets arrive.  If every packet arrives out-of- order, then an immediate ACK SHOULD be sent for every received packet. Similarly, packets marked with the ECN Congestion Experienced (CE) codepoint in the IP header SHOULD be acknowledged immediately, to reduce the peer's response time to congestion events. As an optimization, a receiver MAY process multiple packets before sending any ACK frames in response.  In this case the receiver can determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets. </del>  4.1.

Yes, this was going to be my follow up question. Also NAME what about the ECN paragraph just below it?
NAME The current spec would not cause an ack to be triggered if non-ack-eliciting packets are received out of order. I think that's important. If the packet that was lost was another ack-only packet, this would cause unnecessary acks, and doing this at both ends could cause a minor ack storm. NAME out-of-order is defined in the draft above. I think it's less than ideal, but it's not ambiguous. NAME I've rephrased the ECN section just a bit to be clearer. Does that help?
I'll merge this. If folks have any pending questions, happy to take them on in a new PR.
What does it mean for a subsequent packet to arrive out of order? If I received packets 10, 11, 13, then clearly 13 is out of order. If I received 14 now, would that also be considered out of order? Would 15 be?This text is moving drafts in , so it might make sense to land that first? Or I I can pull this change in after it lands.What about the case where you receive an in order ack eliciting packet and then an out of order ack only packet? Should you still rely on delayed ack timer or send an ack immediately?

the peer sends an ACK immediately when receiving a second ack- eliciting packet.  <ins> In order to accelerate loss recovery and reduce timeouts, an endpoint SHOULD immediately send an ACK frame when it receives an out-of-order packet that is ACK-eliciting.  The endpoint MAY continue sending ACK frames immediately on each subsequently received packet, but the endpoint SHOULD return to acknowledging every other packet after a period of 1/8 x RTT, unless more ACK-eliciting packets are received out of order.  If every subsequent ACK-eliciting packet arrives out of order, then an ACK frame SHOULD be sent immediately for every received ACK-eliciting packet. If a packet is received with the ECN Congestion Experienced (CE) codepoint in the IP header, the endpoint SHOULD respond with an ACK frame immediately, even if the packet is received in order.  Doing so reduces the peer's response time to congestion events. If multiple packets are already available at an endpoint, the endpoint MAY process them all before sending an ACK frame in response.  The endpoint can determine whether an acknowledgement should be sent immediately or delayed after processing the batch. </ins> 4.1.


19.6. The CRYPTO frame (type=0x06) is used to transmit cryptographic <del> handshake messages.  It can be sent in all packet types.  The CRYPTO frame offers the cryptographic protocol an in-order stream of bytes. CRYPTO frames are functionally identical to STREAM frames, except that they do not bear a stream identifier; they are not flow </del>  controlled; and they do not carry markers for optional offset, optional length, and the end of the stream.

Thanks! Beat me to it.
In quic-transport-22 section 19.6 it says CRYPTO frames can be sent in all packet types. However in quic-tls-22 section 4 it says the CRYPTO frames can't be sent in 0-RTT packets. Also in quic-tls-22 section 8.3, similar statement. Maybe the statement in quic-transport-22 needs to be corrected?

19.6. The CRYPTO frame (type=0x06) is used to transmit cryptographic  <ins> handshake messages.  It can be sent in all packet types except 0-RTT. The CRYPTO frame offers the cryptographic protocol an in-order stream of bytes.  CRYPTO frames are functionally identical to STREAM frames, except that they do not bear a stream identifier; they are not flow </ins> controlled; and they do not carry markers for optional offset, optional length, and the end of the stream.