the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token. <del> A token SHOULD be constructed for the server to easily distinguish it from tokens that are sent in Retry packets as they are carried in the same field. </del>  The token MUST NOT include information that would allow it to be linked by an on-path observer to the connection on which it was A client SHOULD NOT reuse a token in different connections.  Reusing a token allows connections to be linked by entities on the network <del> path (see migration-linkability).  A client MUST NOT reuse a token if </del>  it believes that its point of network attachment has changed since the token was last used; that is, if there is a change in its local IP address or network interface.  A client needs to start the <del> connection process over if it migrates prior to completing the handshake. </del>  When a server receives an Initial packet with an address validation token, it MUST attempt to validate the token, unless it has already

This addresses some questions about use of multiple tokens, as well as cleaning up the text adjacent in minor ways.
The spec doesn't define what is a client is supposed to do when it receives multiple NEWTOKEN frames. There seem to be multiple reasonable ways to handle this situation: A client could always use the oldest token first. This would allow the client to establish multiple connections at the same time. A client could replace any old tokens when receiving a new token. This has the advantage that the most recently issued token will (presumably) have the longest life time. Furthermore, if the server encodes connection statistics (RTT, cwnd, etc.) into the ticket, using the newest ticket would provide it with the most recent data. From the Slack discussion, a better way might be a combination of these two: When establishing a new connection, use the most recently obtained token first. When establishing another connection to the same server (before receiving a new NEWTOKEN), use an older token. Client might then want to remember n tokens per endpoint, if they expect to establish n new connections simultaneously (or a few more than n, depending on the network conditions, to account for failed connection establishments).
Your suggestion SGTM.
This is fine, and it merely reflects reality: older tokens are less likely to be useful. But there is no harm in attempting to use any token, provided that it hasn't been used before.
Do we need text about that? NAME do you want me to prepare a PR?
I think that an editorial PR would be great. This is advice that doesn't change interoperability.
Some suggestions, but looks good.

the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token.  <ins> A token SHOULD be constructed in a way that allows the server to distinguish it from tokens that are sent in Retry packets as they are carried in the same field. </ins> The token MUST NOT include information that would allow it to be linked by an on-path observer to the connection on which it was A client SHOULD NOT reuse a token in different connections.  Reusing a token allows connections to be linked by entities on the network  <ins> path; see migration-linkability.  A client MUST NOT reuse a token if </ins> it believes that its point of network attachment has changed since the token was last used; that is, if there is a change in its local IP address or network interface.  A client needs to start the  <ins> connection process over if there is any change in its local address prior to completing the handshake. Clients might receive multiple tokens on a single connection.  Aside from preventing linkability, any token can be used in any connection attempt.  Servers can send additional tokens to either enable address validation for multiple connection attempts or to replace older tokens that might become invalid.  For a client, this ambiguity means that sending the most recent unused token is most likely to be effective.  Though saving and using older tokens has no negative consequences, clients can regard older tokens as being less likely be useful to the server for address validation. </ins> When a server receives an Initial packet with an address validation token, it MUST attempt to validate the token, unless it has already


The three ECN Counts are: A variable-length integer representing the total number of packets <del> received with the ECT(0) codepoint. </del>  A variable-length integer representing the total number of packets <del> received with the ECT(1) codepoint. </del>  A variable-length integer representing the total number of packets <del> received with the CE codepoint. </del>  ECN counts are maintained separately for each packet number space.

Specify that ECN counts in the ACK frame to be in the same pn_space of the ACK frame. Change pseudocode in recovery-draft to reflect that.
This only changes pseudocode and no normative text, so I'm inclined to mark it editorial and merge it.
Are ECN counts in ACK frames per pnspace or unified across pnspaces? I've observed different behaviors of public servers regarding that. [13.3.1. ECN Counts] seems to suggest they are per pnspace: However, [19.3.2. ECN Counts] says: Pseudocode in the recovery draft (Appendix B. Congestion Control Pseudocode) also has a global ecnce_counter.
Thanks for the issue, NAME PR looks good.
Thanks! NAME want to take a look?

The three ECN Counts are: A variable-length integer representing the total number of packets  <ins> received with the ECT(0) codepoint in the packet number space of the ACK frame. </ins> A variable-length integer representing the total number of packets  <ins> received with the ECT(1) codepoint in the packet number space of the ACK frame. </ins> A variable-length integer representing the total number of packets  <ins> received with the CE codepoint in the packet number space of the ACK frame. </ins> ECN counts are maintained separately for each packet number space.


Normally, clients will open streams sequentially, as explained in stream-id.  However, when several streams are initiated at short <del> intervals, transmission error may cause STREAM DATA frames opening streams to be received out of sequence.  A receiver is obligated to open intervening streams if a higher-numbered stream ID is received. Thus, on a new connection, opening stream 2000001 opens 1 million streams, as required by the specification. </del>  The number of active streams is limited by the initial_max_streams_bidi and initial_max_streams_uni transport

The Stream Commitment Attack part was added in draft-ietf-quic-transport-03, when unidirectional streams were not used. In draft-ietf-quic-transport-08, streams were split into unidirectional and bidirectional, but this example of stream commitment attack was missed.
Thanks, NAME I've done some editorializing, and merging.

Normally, clients will open streams sequentially, as explained in stream-id.  However, when several streams are initiated at short  <ins> intervals, loss or reordering may cause STREAM frames that open streams to be received out of sequence.  On receiving a higher- numbered stream ID, a receiver is required to open all intervening streams of the same type (see stream-recv-states).  Thus, on a new connection, opening stream 4000000 opens 1 million and 1 client- initiated bidirectional streams. </ins> The number of active streams is limited by the initial_max_streams_bidi and initial_max_streams_uni transport


use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is <del> carried in the NEW_CONNECTION_ID frame sent by either peer, and servers can specify the stateless_reset_token transport parameter during the handshake (clients cannot because their transport parameters don't have confidentiality protection).  This value is protected by encryption, so only client and server know this value. Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </del>  An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset <del> Tokens that are associated with connection IDs that the endpoint recently used to send packets from the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </del>  NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key. <del> The same Stateless Reset Token MAY be used for multiple connection IDs on the same connection.  However, reuse of a Stateless Reset Token might expose an endpoint to denial of service if associated connection IDs are forgotten while the associated token is still active at a peer.  An endpoint MUST ensure that packets with Destination Connection ID field values that correspond to a reused Stateless Reset Token are attributed to the same connection as long as the Stateless Reset Token is still usable, even when the connection ID has been retired.  Otherwise, an attacker might be able to send a packet with a retired connection ID and cause the endpoint to produce a Stateless Reset that it can use to disrupt the connection, just as with the attacks in reset-oracle. </del>  Note that Stateless Reset packets do not have any cryptographic protection.

This forbids the use of duplicate stateless reset tokens. It also cleans up some of the related language about matching of tokens. .
What next steps should be taken here? Is this ready for a consensus call?
NAME : I expect that the chairs will put out a consensus call soon.
As part of the discussions on / , we've found that reusing stateless reset tokens (SRTs) can allow attackers that delay packets to close connections. Even though the text in does a good job explaining why you shouldn't reuse SRTs, I don't see any reason why one would want to reuse SRTs across connection IDs in the same connection. Therefore I would prefer we ban reusing them, and make it a PROTOCOL_VIOLATION to do so. Apologies for not raising this on , I had not thought about the problem at that time.
I do not think you missed anything. Instead, you spotted the issue at the exact moment; it's my understanding that is introducing this issue. There is no problem in the current design, because receiving a packet with an outdated CID is not expected to cause a stateless reset. Regarding why you'd want to reuse a SRT, the simple reason is because you do not need to create different SRTs for every Connection ID. A QUIC stack that generates a CID by a construction like 1 can use as a construction for generating SRT. For QUIC stacks that aren't interested in retiring CIDs (which seems to be the majority for me), this construction makes sense. I think my preference would be to retain the design; it's just about adding a warning when you want to retire a CID. 1: path_index is a sequence number of path for each connection
I don't think introduces this issue. It does make it worse but the issue was already present. It's reasonable for a server implementation to drop a CID from their state when the client retires it. There are two options to guarantee safety: make sure each CID has a distinct SRT make sure once a CID is sent in NEWCONNECTIONID it always routes back to this connection I think (1) is simpler and giving implementations two options here not only makes the spec more complicated, but can also lead to accidental vulnerabilities. I think it would be safer for the implementations you mention to use to generate their SRT. That also makes it simpler to generate the SRT from the cleartext CID. In general, it's been this spec philosophy to add constraints that can be verified by the peer whenever possible. Forcing distinct CIDs ensures verifiable security whereas requiring that CIDs get routed appropriately for the duration of the connection is not enforceable and therefore easier to get wrong.
Essentially, the current text says that an issuer can't actually forget a retired CID until all CIDs with the same SRT have been retired. At that point and not before, you can forget all of them at once. doesn't change this at all -- doesn't even really make it worse. That PR provides a mechanism for asking a client to retire a certain set of CIDs; if the set only partially overlaps with the set of CIDs covered by an SRT, that mechanism is not terribly useful because you can't actually forget all the CIDs you asked the peer to retire. It could be very useful to ask the peer to retire the last CID in one of these groups because you've got 200 of them you'd really like to clear out. But that doesn't change the core fact: An issuer can't actually forget a retired CID until all CIDs with the same SRT have been retired. If each SRT maps uniquely to a CID, this is a simple requirement to satisfy. If you choose to do something else, that's your problem.
I think what NAME says is correct. OTOH, I now think that forbidding reuse of SRT might help the receiver of the stateless resets. Without the prohibition, when retiring a CID, an endpoint needs to consult other SRTs that have been issued for the same connection to see if it can unregister the SRT corresponding to the CID being retired, or use a ref-counted hashmap for maintaining the mapping from SRTs to connections. We might argue that this requirement is easy to miss, and hard to test. Having the prohibition removes this requirement and therefore can be considered as a simplification. Contrary to that, we might argue that allowing the reuse of SRT has marginal benefit. It is trivial to construct different SRT for each CID, as pointed out by NAME
NAME analysis is entirely correct. I don't think that we should put in rules to prevent self-harm through idiocy. And this is definitely a case of that. An endpoint that forgets a CID when the associated SRT is active only hurts themselves. The text is sufficient as it stands. A prohibition won't prevent implementations from doing dangerous things. As it stands, you need to take extraordinary steps to put yourself in position to mess this up. The construction NAME describes is a totally sensible one, but it is very hard to forget connection IDs with that scheme. More relevant to this, the SRT construction process described in the spec won't have this problem. What exactly do you think this prohibition will achieve?
If there is a prohibition, an endpoint can remove the SRT of the CID being retired, without consulting the SRTs of other CIDs belonging to the same connection. While incorrect in terms of current spec, such design works flawlessly with endpoints that issue different SRT for each CID. It seems that most implementations would issue different SRT for each CID. Then, there's chance that we might see stacks implementing this incorrect but simpler approach. Having the prohibition eliminates the chance of seeing these "incorrectness."
I agree that we should remove the text that allows the reuse of SRTs from the spec (In fact, last night I was planning to open exactly this issue, and woke up this morning to see that NAME already did so while I was asleep). I don’t see any reason why you’d want to reuse a SRT in the first place - computing one HMAC shouldn’t be more expensive than applying the packe protection to send a single packet. With regards to how you store connection IDS, this design is only simpler if you do the wrong thing and don‘t keep track of expired connection IDs that still have active SRTs. Doing it the right way will most likely result in a more complex design than associating each CID with its own SRT. In short: it’s easy to mess up, and there’s little benefit, so let’s remove it.
I'm not totally against a unique SRT per CID, but I am concerned about the static memory use of otherwise passive connections. If a CID and SRT can be 255 bytes each (depending on where discussions go) that is half a kilobyte per CID, and there might be 8 of those, possibly more. That is 4K per connection even if you retire all active buffers because there isn't currently any traffic.
NAME SRTs are 16 bytes long
NAME I'm not sure about that - isn't there text about hiding SRT among ordinary traffic. If so it cannot be shorter than the CID + overhead, well the packet that is. But I'm probably mixing up packet size with storage requirements.
The SRT is only 16 bytes, but the packet it's sent in is supposed to look like a typical QUIC short header packet, and therefore needs to be of a minimum size.
So essentially, we're saying that we maybe didn't really have consensus on the answer to being "yes." But since there was a consensus call that said we did, now the question is whether we have consensus to reverse that decision. Let me suggest that be taken to the list.
Discussed in Montreal, next step is for NAME to do a PR and follow normal process
Conclusion: prohibit the use of duplicate tokens. Allow, but not require the use of PROTOCOL_VIOLATION if a duplicate is detected.
covered this.
The current text does not explicitly say that endpoint has to check the stateless reset token received in NEWCONNECTIONID (and server's TP if endpoint is a client). then compare the last 16 bytes of the packet with all Stateless Reset Tokens that are associated with connection IDs that are currently in use. This includes Stateless Reset Tokens from NEWCONNECTIONID frames and the server's transport parameters. With this wording, I was under the impression that server can use stateless reset token in its own TP, but it seems not. I think it would be nice to state that only received stateless reset tokens are checked.
carried in the NEWCONNECTIONID frame sent by either peer, and servers can specify the statelessresettoken transport parameter during the handshake (clients cannot because their transport parameters don't have confidentiality protection). This text is also problematic because it suggests that endpoint can specify token sent by either peer.
The first of these is already addressed in the draft, though it could be improved.

use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is  <ins> carried in the Stateless Reset Token field of a NEW_CONNECTION_ID frame.  Servers can also specify a stateless_reset_token transport parameter during the handshake that applies to the connection ID that it selected during the handshake; clients cannot use this transport parameter because their transport parameters don't have confidentiality protection.  These tokens are protected by encryption, so only client and server know their value.  Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </ins> An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset  <ins> Tokens corresponding to active connection IDs that the endpoint has used for sending packets to the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </ins> NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key.  <ins> The same Stateless Reset Token MUST NOT be used for multiple connection IDs.  Endpoints are not required to compare new values against all previous values, but a duplicate value MAY be treated as a connection error of type PROTOCOL_VIOLATION. </ins> Note that Stateless Reset packets do not have any cryptographic protection.


When an endpoint issues a connection ID, it MUST accept packets that carry this connection ID for the duration of the connection or until its peer invalidates the connection ID via a RETIRE_CONNECTION_ID <del> frame (frame-retire-connection-id). </del>  An endpoint SHOULD ensure that its peer has a sufficient number of available and unused connection IDs.  Endpoints store received send to a value that has not been used on another path. An endpoint MUST use a new connection ID if it initiates connection <del> migration.  Using a new connection ID eliminates the use of the connection ID for linking activity from the same connection on different networks.  Header protection ensures that packet numbers cannot be used to correlate activity.  This does not prevent other properties of packets, such as timing and size, from being used to correlate activity. </del>  Unintentional changes in path without a change in connection ID are possible.  For example, after a period of network inactivity, NAT reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently. <del> An endpoint that exhausts available connection IDs cannot migrate. To ensure that migration is possible and packets sent on different paths cannot be correlated, endpoints SHOULD provide new connection IDs before peers migrate. </del>  9.6.

This closes the rather serious hole we left when we attempted to limit the effect of perpetual changing of connection IDs. This uses a narrower formulation, that I believe will avoid perpetual changes. But it does require reciprocal connection ID changes where endpoints genuinely do migrate. It's a design change unfortunately, but I hope non-controversial.
Latest changes look great!
There are bunch of changes in migration recently, and they suggests that new CID is required only when endpoint sends a packet from new local address and an endpoint of migrating peer is no longer required to change its CID when sending a packet using new path as long as its local address does not change. But there are several lines of text left to suggest that an endpoint changes CID: Are they just remnants of old text and should be removed? or does changing CID in response to migration still a requirement?
Changing CID in response to migration is a soft requirement since it cannot always be enforced since NAT rebinding might happen without endpoint interference. It can hurt privacy though. CID changes can also happen without migration. One reason is for the peer to do so after an idle period because the risk of NAT rebinding is high. Another reason is to ensure CIDs do not get stale on long living connections since the peers routing infrastructure might regroup or move valid CID patterns to a new epoch. The last reason could perhaps be replaced by the proposed explicit retire prior to facility.
So what does implementation actually need to do in response to migrating peer? Just keep using same CID or not? Use same CID to do path validation? I know that peer can change CID at anytime it likes.
The key principle is to not reuse a CID on a new path if it can be avoided. I'm not sure about the CID terminating if the pool is exhausted - in your quoted text. I think it may choose to do so (and is encouraged to do so) but I'm not sure it is required to. Changing CID in response to a peers migration is therefore relevant but changing CID just because the peer changes CID is problematic because it might never end.
Thank you. I thought that new CID should be used on new path, but I couldn't find normative text for it now. Other than quoted text, draft says new CID is required if endpoint migrates to new local address. I remember the discussion of changing CID in response to CID change which is not required and sorted out with the recent PRs.
I think at one point we said "SHOULD change if the peer changes, but be careful not to loop". Otherwise it's pretty trivial to establish linkability, so everything is structured about trying to make sure you can do it, but not falling over too hard in the edge cases where you may not be able to.
We agreed that changing CID wouldn't trigger a change in response, but the only hard rule was "if you use a new path, use a new connection ID".
Ah, interesting. I thought you were still expected to attempt to change if you could.
... if you know that you change path - but you don’t always know.
Is there an underling issue here, or has the question been answered?
I couldn't find the hard rule "if you use a new path, use a new connection ID" in the draft in clear manner. What I found is endpoint MUST change CID when using new local address. It does not mention about change on remote address.
Background context for this issue: In NAT rebinding test, quic tracker does not send any NEWCONNECTIONID. My server implementation refuses to use new path because there is no new CID left and cannot verify new path assuming that new CID is required for new path. But they say that using NEWCONNECTIONID for new path is not written in draft as requirement, so server should follow the new path. If using new CID for new path is a requirement, quic tracker should send NEWCONNECTIONID. If it is just a recommendation, server probably should use new path with same CID.
I think the issue is that there are two types of migration: "Direct" migration, where an endpoint changes to a new path and it is in their interest to also change the CID (to reduce trackability), and "indirect" migration, i.e., NAT rebinding, where a middlebox changes the path for an endpoint in a way that they are unaware of and therefore cannot initiate a CID migration for. Both of there are valid, but they are slightly different. Is it sufficient to clarify that for a direct migration, a CID change SHOULD happen, and for an indirect migration, it (generally) cannot, and that the peer must handle both?
NAME I like the way you categorize migration. Does that mean in practical sense that clients SHOULD change CID on direct migration, and that servers SHOULD respond to a CID change with a CID change? Assuming that is what we are seeking, I think it might be a good idea to clarify using a non-RFC2199 term that is effectively what we recommend (I understand that we prefer talking about "endpoints").
Client doesn't change CID in indirect migration because it does not aware of it. Regardless of direct or indirect migration, server knows the packet is coming from new path (new remote address and/or port). So the question is server MAY/SHOULD/MUST change CID when sending a packet to new path. Current draft says that it is MUST to change CID on direct migration:
An option would also to keep MUST change CID on direct migration and add that a en endpoint SHOULD not reject a migration that doesn't change CID because it might be a valid indirect migration. An endpoint MAY, however, reject indirect migrations that do not change CID if this is privacy is considered more important than broad interop (this is controversial).
Either way, I think it would be beneficial if the text has a section with clear definition of the terms "direction migration" and "indirect migration".
NAME bit when a server sees the client using a new path (because of a NAT rebind) the server is not the one initiating the migration
In reviewing this, I think we've messed up badly. We currently only mandate the use of a new connection ID for the initiator of a migration. A peer that responds to a connection migration has to change a connection ID if the initiator changed their connection ID. That needs to be fixed promptly. Then the concerns that NAME raises here might seem less surprising. NAME NAME can we recategorize this as design? Thanks.
This has been discussed at length and while I tend to agree that this is not a good design, I believe things ended up as they are due to concerns about endless CID change loops. I think that can be prevented without too much complexity, but that argument was one of the drivers, as I recall.
The problem was that we over-rotated. Now we don't require any change on the responding endpoint's part, which means we have near-perfect linkability across migrations. I think that the proposed change is the minimal one we can mandate. I might prefer to have something slightly better, but doing that without also flirting with the infinite loop was too hard.
Agreed. IIUC, the only endpoint that "initiates" a CID change is the client, because only the client side that migrates (or probes) a new path. This is the case for SPA too. Therefore, I believe that stating something along the lines of the following is sufficient: a server MUST respond with a new CID to a packet that has been received on a new path with a new CID.
I definitely agree that we should change this to require a change in response, it would be worth wordsmithing something that achieves this -- the PR is a good start. Differentiating by client/server might be interesting, although there appear to be two cases: A CID change on a link without any address change. A change in any part of address (IP/port) We can talk about initiator/responder to migration rather than client/server and that might be a good approach (which is pretty close to what the PR does right now).
I thought where we landed (in concept, not necessarily text) was that you MUST change CID any time you're using a different local or remote IP:port. That means the client MUST change if it knows it's migrating (new source address); the server will change in response because it's going to a new destination. The client SHOULD change if it has been quiescent and anticipates a NAT-rebind (i.e. uncertain about source address); if it actually got rebound, the server would change as well (new destination address). If it turns out the client didn't get rebound, then IP:port already correlate it; perhaps slightly better if the server rotates, but it's not required to.
LGTM. Thank you for the changes.Thanks for writing this up! Latest content looks great, just two minor wording questions and this looks ready to go.

When an endpoint issues a connection ID, it MUST accept packets that carry this connection ID for the duration of the connection or until its peer invalidates the connection ID via a RETIRE_CONNECTION_ID  <ins> frame (frame-retire-connection-id).  Connection IDs that are issued and not retired are considered active; any active connection ID can be used. </ins> An endpoint SHOULD ensure that its peer has a sufficient number of available and unused connection IDs.  Endpoints store received send to a value that has not been used on another path. An endpoint MUST use a new connection ID if it initiates connection  <ins> migration as described in initiating-migration or probes a new network path as described in probing.  An endpoint MUST use a new connection ID in response to a change in the address of a peer if the packet with the new peer address uses an active connection ID that has not been previously used by the peer. Using different connection IDs for packets sent in both directions on each new network path eliminates the use of the connection ID for linking packets from the same connection across different network paths.  Header protection ensures that packet numbers cannot be used to correlate activity.  This does not prevent other properties of packets, such as timing and size, from being used to correlate activity. </ins> Unintentional changes in path without a change in connection ID are possible.  For example, after a period of network inactivity, NAT reset its congestion state (see migration-cc), so the port SHOULD only be changed infrequently.  <ins> An endpoint that exhausts available connection IDs cannot probe new paths or initiate migration, nor can it respond to probes or attempts by its peer to migrate.  To ensure that migration is possible and packets sent on different paths cannot be correlated, endpoints SHOULD provide new connection IDs before peers migrate; see issue- cid.  If a peer might have exhausted available connection IDs, a migrating endpoint could include a NEW_CONNECTION_ID frame in all packets sent on a new network path. </ins> 9.6.


HTTP/3 endpoints contain a QPACK encoder and decoder.  Each endpoint MUST initiate at most one encoder stream and at most one decoder stream.  Receipt of a second instance of either stream type MUST be <del> treated as a connection error of type HTTP_WRONG_STREAM_COUNT.  These streams MUST NOT be closed.  Closure of either unidirectional stream type MUST be treated as a connection error of type </del>  HTTP_CLOSED_CRITICAL_STREAM. An endpoint MAY avoid creating its own encoder stream if it's not

On receiving duplicate QPACK streams, the error should be HTTPSTREAMCREATION_ERROR which matches with control stream duplicates.
Technically should have a corresponding issue, but yes. Nice spot!

HTTP/3 endpoints contain a QPACK encoder and decoder.  Each endpoint MUST initiate at most one encoder stream and at most one decoder stream.  Receipt of a second instance of either stream type MUST be  <ins> treated as a connection error of type HTTP_STREAM_CREATION_ERROR. These streams MUST NOT be closed.  Closure of either unidirectional stream type MUST be treated as a connection error of type </ins> HTTP_CLOSED_CRITICAL_STREAM. An endpoint MAY avoid creating its own encoder stream if it's not


transport parameter is only sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other <del> family. </del>  The maximum number of connection IDs from the peer that an endpoint is willing to store.  This value includes only connection

Byte order of IP addresses in PreferredAddress is not explicitly stated. In practice, most OSs keep them in network byte order, and the spec says integers are in network byte order, however the fields for ip addresses are defined as opaque data. The byte order should be stated on this spec.

transport parameter is only sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other  <ins> family.  IP addresses are encoded in network byte order. </ins> The maximum number of connection IDs from the peer that an endpoint is willing to store.  This value includes only connection


bytes. An opaque blob that the client may use with a future Initial <del> packet. </del>  An endpoint might receive multiple NEW_TOKEN frames that contain the same token value.  Endpoints are responsible for discarding duplicate

If we allow empty NEW_TOKEN frames, the receiver would have to check that the token is not empty before saving and reusing it, so it seems slightly easier to forbid this nonsensical edge case.
Like NAME I would like the spec to specify the error code. I don't care which it is.
Consensus was declared on this PR a couple of days ago. Can we get this merged, and deal with the error code requested by NAME and NAME as part of ?
In the context of deciding what code to use for badly formatted frames: Originally posted by NAME in URL I tend to agree with this. Let's go through and try to apply some sort of consistency. Maybe even try to be principled about it.
How does this principle sound: If you can detect an error while parsing the frame, and without consulting any connection state, it should be a FRAMEENCODINGERROR. For everything that happens after frame parsing, we use the more concrete error types (or PROTOCOL_VIOLATION).
+1. To rephrase, as the name suggests, FRAMEENCODINGERROR indicates a syntactic (i.e., encoding) error. But nothing else.
I'd like to see a PR, but that seems like a good principle.
NAME I prepared a PR, just waiting for to get merged to apply the principle there.
Can we call this editorial?
It changes functional requirements about what error codes to send, so not really.
Discussed in Cupertino. Will be consensus-called.
It makes absolutely no sense to allow NEW_TOKEN to be empty, but we never really said that explicitly. So let's do that. NAME opened for that. Unfortunately, this is technically a design change, and we are that point. So let's do this by the book.
Would a token of length 1 be any better? We already have that tokens must be unique and different from other token types.
Yes. At least then we have a clear signal that a token was provided. Now, it's probably still awful (though it might be stateful and so not completely awful), but absent some strong signal about how short things can be we're in much the same state we are with connection IDs. We now allow those to be 1 byte long, with similar results.
My weak preference is to not forbid 0-byte tokens, as it would be something that stacks would be forced to implement, just to pass the conformance tests. It does not provide us any benefit (as NAME points out), while requiring stacks to have the check implemented.
NAME I see the point. There is already text in initial packets stating "Token Length: A variable-length integer specifying the length of the Token field, in bytes. This value is zero if no token is present. "
Allowing 0-length token requires the receiver to filter them out before saving them to their token store (however that is implemented) for reuse in a connection in the future. If you don't do this, you might go and retrieve a token from the token store, use it on an Initial packet, just to end up in the situation that you're actually not using a token at all. I disagree with NAME here. A stateful implementation could for example generate tokens by drawing random numbers between 0 and (a big value of) N. Occasionally, this will result in small token lengths, without compromising on the desired privacy properties.
NAME there is nothing to disagree with, or thumbsdown - it was a question.
Fair enough, that's a legitimate question, and I hope that I could provide a satisfying answer. Removed my thumbsdown.
NAME That's a good point. That makes sense - I can see the value of using "0" as a signal to indicate the "absence" of the token throughout the specification, considering the fact that it is used that way at some parts of the spec.
I'm not sure why you'd want to do this, but an empty NEW_TOKEN could be used to indicate the past tokens are no longer valid, but the peer isn't getting a new one(at least yet). That is the practical implication of the way it's written today(later tokens are likely better than earlier, etc).
Yeah, that's just confusing. The semantics of NEWTOKEN don't imply invalidation. We'd have to explicitly say that if URL == 0, all previous tokens are invalid. And then you have to also address the case of reordering and retransmissions of old NEW_TOKEN frames that arrive after the empty one. Let's not go there.

bytes. An opaque blob that the client may use with a future Initial  <ins> packet.  The token MUST NOT be empty.  An endpoint MUST treat receipt of a NEW_TOKEN frame with an empty Token field as a connection error of type FRAME_ENCODING_ERROR. </ins> An endpoint might receive multiple NEW_TOKEN frames that contain the same token value.  Endpoints are responsible for discarding duplicate


values, which might be used to link connection attempts; see validate-future.  19.8. STREAM frames implicitly create a stream and carry stream data.  The

This doesn't add rationale. It doesn't have to. The rationale isn't high-minded, it's just motivated by a desire to complete this protocol.
Should the server just ignore it, or is is a protocol violation?
I vote for protocol violation.
The spec says nothing...
Yeah, there's no need to vote, this is just an omission. A protocol violation (or similar) is what we should mandate.
Playing devils' advocate: what about p2p apps? The token says, you can call me back from that address, I will accept it. The client is not saying anything untrue.
I'll bite. Yes, a P2P app that used mutual authentication in a way that would allow a former server to associate a token with an attempt to connect to a peer that was previously a client, might be able to use NEW_TOKEN. That's a little outside our remit. I'm going to take this as an editorial change on the understanding that a design issue could be opened to allow the asymmetry. Anyone who is serious about that should also consider the fact that NewSessionTicket in TLS is potentially symmetrical by the same logic. Which suggests that 0-RTT isn't out of the question either.
On p2p there is the potential problem of relating a token to previously stored settings and observables from a previous session. This isn't well defined if the client specifies a new token. So perhaps it is better for each peer to "waste" a single 1-RTT connection the first time in a given direction.

values, which might be used to link connection attempts; see validate-future. <ins> Clients MUST NOT send NEW_TOKEN frames.  Servers MUST treat receipt of a NEW_TOKEN frame as a connection error of type PROTOCOL_VIOLATION. </ins> 19.8. STREAM frames implicitly create a stream and carry stream data.  The


Retry packets (packet-retry), Version Negotiation packets (packet- version), and packets with a short header (short-header) do not contain a Length field and so cannot be followed by other packets in <del> the same UDP datagram. </del>  12.3.

They don't have a length field, so the only place they could go is at the end of a packet. As the spec currently stands, it's valid to send a packet like this. There's little value in doing that. Do we want to continue to allow this?
My understanding is that they are implicitly forbidden, because coalescing packets belonging to different connections are forbidden, and because there’s nothing to coalesce with when either of the two types of the packets is sent. I am not sure if we need to be more clear on that, considering the fact we generally use MAY for the discarding rules for coalesced packets.
VN certainly causes a new connection based on discussions in Tokyo. Retry does not cause a new connection(ie: same ClientHello). Clarifying this seems wise if it's not already clear.
Yes, however when a Retry packet is being sent, there's nothing a server can send along with. Regarding if we need to clarify, while I do not think it's absolutely necessary, I can see that people might want to have it stated considering the fact that we do utilize corner case rules of coalesced packets (see ).
So, is this editorial?
I'm with NAME but I'm still wondering: what is the clarification that we can provide?
NAME Simply clarifying that "VN and Retry packets will never be coalesced" as part of the changes we would make in might be a good idea. We do not need to use a RFC 2119 word here due to the editorial nature of the issue.

Retry packets (packet-retry), Version Negotiation packets (packet- version), and packets with a short header (short-header) do not contain a Length field and so cannot be followed by other packets in  <ins> the same UDP datagram.  Note also that there is no situation where a Retry or Version Negotiation packet is coalesced with another packet. </ins> 12.3.


4.1.4. As keys for new encryption levels become available, TLS provides QUIC <del> with those keys.  Separately, as TLS starts using keys at a given encryption level, TLS indicates to QUIC that it is now reading or writing with keys at that encryption level.  These events are not asynchronous; they always occur immediately after TLS is provided with new handshake bytes, or after TLS produces handshake bytes. </del>  TLS provides QUIC with three items as a new encryption level becomes available: process those acknowledgments in order to detect lost Handshake packets.  4.1.5. exchange-summary summarizes the exchange between QUIC and TLS for both client and server.  Each arrow is tagged with the encryption level used for that transmission.  4.2. This document describes how TLS 1.3 TLS13 is used with QUIC.

URL a TLS stack. The canonical example being the Handshake keys, which are needed by a client to acknowledge partial server Handshake flights. TLS won't use those keys itself until it has the entire set of Handshake messages from the server. In doing this, I decided that it makes sense to reorder the sequence diagram to show what incoming packets trigger different events.
In order for the client to be able to acknowledge packets sent with Handshake encryption level, it needs to have access to the Handshake write key right after processing the ServerHello. URL states that the Handshake write key is exported after receiving the server's Finished message, which would make loss recovery of Handshake packets impossible. I found this fact non-obvious, and I think it should be documented in some way that we're deviating from the schedule in which a normal TLS 1.3 implementation would make new keys available.
I had the same realization a while ago, but failed to capture that in the spec. It's a fairly important point that - if ignored - would lead to bad outcomes, because a good part of the time you can get away with not exporting it early. You mostly just get transient errors... until the server can't fit its certificate into 3 packets, then you get a hard fail.

4.1.4. As keys for new encryption levels become available, TLS provides QUIC  <ins> with those keys.  Separately, as keys at a given encryption level become available to TLS, TLS indicates to QUIC that reading or writing keys at that encryption level are available.  These events are not asynchronous; they always occur immediately after TLS is provided with new handshake bytes, or after TLS produces handshake bytes. </ins> TLS provides QUIC with three items as a new encryption level becomes available: process those acknowledgments in order to detect lost Handshake packets. <ins> QUIC also needs access to keys that might not ordinarily be available to a TLS implementation.  For instance, a client might need to acknowledge Handshake packets before it is ready to send CRYPTO frames at that encryption level.  TLS therefore needs to provide keys to QUIC before it might produce them for its own use. </ins> 4.1.5. exchange-summary summarizes the exchange between QUIC and TLS for both client and server.  Each arrow is tagged with the encryption level used for that transmission. <ins> exchange-summary shows the multiple packets that form a single "flight" of messages being processed individually, to show what incoming messages trigger different actions.  New handshake messages are requested after all incoming packets have been processed.  This process might vary depending on how QUIC implementations and the packets they receive are structured. </ins> 4.2. This document describes how TLS 1.3 TLS13 is used with QUIC.


10.4.1. <del> An endpoint detects a potential stateless reset when an incoming packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset Tokens corresponding to active connection IDs that the endpoint has used for sending packets to the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated </del>  with connection IDs it has not used or for connection IDs that have been retired. <del> If the last 16 bytes of the packet values are identical to a </del>  Stateless Reset Token, the endpoint MUST enter the draining period <del> and not send any further packets on this connection.  If the comparison fails, the packet can be discarded. </del>  10.4.2.

Otherwise information about the token might leak. As NAME says, there is no strict need to compare across the entire set of values. That could leaks two things: that the inbound packet was dropped and the total number of stateless reset tokens. Both are things that we might care about, but will probably find prohibitive to protect.
On consideration, I'm going to make this a design issue so that it gets under noses.
Maybe I'm missing something obvious here, but how is this easy to implement? In my implementation, I have a map of reset tokens -> connections. Unless I switch to a constant time map implementation (the standard library doesn't provide one, you'd have to write that yourself), the comparisons are (probably?) not constant time. Given that this would cause some considerable complexity, I don't think it's worth defending against this rather uninteresting attack, and I'm not sure if a MUST is the right way to go here.
To support NAME point, a SHOULD and a comment about why you should do it and the impact of not doing so seems fine to me.
One thing that I'm trying to protect is the integrity of the specification and the analysis that (I hope) will come. If we allow weaseling out of this sort of requirement, then the analysis has to allow for that too. If you are using a hash map, then you can do as NAME suggests: use a PRP (a PRF would also work) to produce an intermediate value from the incoming value and compare that in non-constant time. As long as the operation cannot be reversed by an attacker and the secret input to the PRP (i.e., the token) are handled in constant time, the secret doesn't leak. We can't assume that resetting a connection is without cost. QUIC has the nice property that connections can survive network flutters, which means that a lot of investment could be made in a connection. (That's probably still inadvisable.) A well-positioned attacker doesn't have to perform a huge amount of work to recover a stateless reset token, relative to the value they might gain in disrupting a "high-value" connection of that sort. If we use a "MUST", then we're not stopping someone from deciding that this requirement doesn't apply to them. But they would take responsibility for that, hopefully with some awareness of what is being risked. Yes, we could use "MUST avoid leaking through timing side channels unless ", but determining what X needs to be is tricky as it depends so much on application protocol and other contextual stuff.
NAME As I stated on the issue, using a "standard hash map implementation" for storing stateless reset tokens as-is is not a good approach, as it might be vulnerable to . I think this is a unique property for a hash map containing stateless reset tokens; hash maps containing CIDs is not susceptible to the issue as CIDs are chosen by the endpoint (rather than the peer), nor are the hash maps containing streams, as stream IDs are generated in sequential order (assuming that an endpoint would not have a very high stream concurrency per each connection). Considering that, we might want to state something like the following in Security Considerations: As the stateless reset tokens are controlled by the peers, QUIC endpoints SHOULD be resistant to hash flooding DoS attacks when using a hash map for retaining those tokens. One way of achieving such property is to retain and compare the transformed values of the stateless tokens where the transformation is defined as a cryptographically-secure pseudo-random permutation (e.g., block cipher) or a pseudo-random function (e.g., secure hash function), instead of using the raw token values as the hash keys. This approach also satisfies the constant time comparison requirements in section x.y.
A PR that said that might be worth talking about. Nit: You need the "secure hash function" to be keyed in some way.
NAME Fair enough. Created .
NAME I was wrong on this, and you're absolutely right. Implementation difficulties shouldn't make us lower our security guaranties, or the language we use to describe them. NAME Thanks for the pointer to the hash flooding attack. By the way, here's the corresponding to the slides you linked.
Rust has inbuilt defenses against hash flooding, and some languages have that built in. Note that this is something that doesn't guarantee constant time over inputs, as that is generally not a design goal, though many schemes have that naturally. Even then, the functions used might be trivially reversible.
This will make the process more robust, as the process of splitting a datagram into packets could cause the token to be split. We recommend sending a packet (and one that can't be coalesced to boot). This was always going to be a single packet datagram anyway, so this shouldn't affect implementations much.
Not sure if this is editorial or not. We already require sending to be a whole datagram: So this could be seen to match things up. But it could require code changes in implementations, so I'm leaning toward .
Discussed in Cupertino. to be merged following consensus call.
Why does the text mandate this order? You need a constant time comparison in any case.
I'm not sure the order is (intended to be) normative. An SR looks like a junk packet that you would otherwise drop, either because it doesn't match a known connection by CID, or because it happens to match a known CID (zero-length helps) but gives a whacko packet number compared to the connection state, or because the packet number looks reasonable but the MAC is bad. In those situations, you then have to decide whether it's just junk or actually a SR. So the order is what we expect a logical implementation would do, but not necessarily required. Can you propose better explanatory text for that?
Sure, but I have to get my other issues from my read-through in first.
NAME I know that this is now old, but I think that there's something here. Not in the ordering, which I think is not important, but in the constant time piece. I think that this implies that constant-time comparison with every viable stateless reset is something we should do on every packet. There's been an assumption throughout that packet processing time for dropped packets isn't observable. I think that's a bad assumption, frankly. It's not like you can't put a packet with a known processing time after each probe. So I think that we've got to at least say that this is what happens. But we've just raised the packet processing load considerably, so I doubt that this will be well received.
Depending on implementation it can be very hard to do constant time. Even if you compare keys in constant time, the cache latency can be much higher. For stateless resets I'd assume a lookup only happens once and then it is removed from, say, a hash table. That is, there is no cache latency difference between a valid and an invalid lookup. Compare this to connectionsID's that are looked up frequently. Here it is possible to guess to valid CID's based on lookup times. But even if you do not have cache latency issues, you have hash collision issues. This makes a hash table unsuitable. The alternernative is binary tree, or a trie. The binary tree will be have to modified to do log N comparisons for all lookups even if the key is found higher up. And regardless a binary tree is much much slower than a hash table. Tries are very complex to implement correctly and must still be implemented for maximum search path comparison. Another approach is to timestamp the lookup and busyloop until you reach, say, 100ns of cpu cycles after performaning the lookup in a hash table. This is still faster than a binary tree, but slower than the trie. I seriously doubt most implementations would go out of their way to make resets effectively constants time. So the question is - does that make most implementations trivial targets for DoS resets? And what about guessing CID's? How can practically protect against this, because trusting the implementation to be constant time is not realistic.
A related problem is the use of API access tokens. Many people argue against JWT token validation because you cannot retire a JWT token. But a JWT token is easy to verify in constant time. An opaque access token must be looked up, and this is very hard to do constant time. This could be major security concern on the internet at large.
Thus, a solution to the reset problem could be to define an algorithm and share keys with the client. The problem is that the clients key must be session specific while that server cannot share its server keys, so something creative must be invented. If this is done, another latency will be added to the per packet overhead, which is also not desirable, similar to header protection.
I imagine this to be much simpler than you suggest. An endpoint that sends packets using a given set of connection IDs puts all the associated stateless reset tokens into a structure. Then along with decrypting packets, it iterates through that structure and performs a constant time comparison with each. That's something that won't have weird cache usage patterns, other than the usual cost of getting these values into the cache if they fall out. The number of active values shouldn't be a secret, which is most of what that tells you. The result of this will be two Boolean values: whether the AEAD was OK and whether the stateless reset was triggered. An authentic packet will be the logical OR of those two values. Anything else is dropped. After that point, you have crossed into packets being "good", so the time you take to process the packet is less of a concern.
If you can guarantee that there are only a short list to search, then yes. But it is only per connection ID that list is short, so you need to make the connection ID lookup constant time. You can potentially have much more than 100K connection ids to map. For normal operation you first validate the packet, so here the CID lookup is less critical, but for reset that is not the case. Clearly performing a linear search over all CID's is not viable.
I think that the list could be narrowed by path. If you have tons of connections on the same path, yeah, that's not great.
You don't need constant time because you are not replying to the reset. Worst case is that you ignore / reject the reset and continue sending observable packets. The timing of those packets is largely (but not entirely) independent of the reset lookup timing. Further, if a reset is successfully guessed, you are dead, so the only thing to protect is how you test for and react to unmatched resets. If packets are paced you should be good.
That assumes that you don't have observable behaviour that follows the processing of that packet. What I'm concerned about is being able to measure the time to compare, which gives an attacker a read on the value of the stateless reset token. I think that it is sufficient for the comparison of each stateless reset token to be constant time, but the process might be cut short if a match is found. And it might be OK to not check stateless reset tokens if the packet decrypts successfully. That would all be on the basis that we're protecting multiple discrete items and not a single secret.
A practical solution to the problem is to sample dropped packets and process these during idle time and eventually tear down a connection if necessary, rather than immediately. It can also be done in a separate thread although that on its own will not fully hide timing.
While I agree that endpoints should not leak information that let's the attacker guess a valid token value, I am not sure if we need to require constant time comparison, as suggested by . I think that the practical defense would be mix a secret seed securely into the hash key (e.g., by using HMAC), much like we do for hash tables to prevent DoS collision attacks. Assuming that we mix a secure seed, the attacker would not be possible to deduce information from the response time of the hash table.
Assume that you do have a way of measuring how long it takes to process a garbage packet. Also assume that there is just one token. If you don't compare in constant time, the attacker can measure how long it takes to compare the possible token with the real token. For every byte that matches, you take a tiny bit longer. A complete search takes just 2^12 guesses, multiplied by any oversampling factor you might need to extract the side channel information. That makes it not very hard.
But if you need that many guesses to kill a connection, it is not viable. If it is to guess a master secret, the solution is to use a one way hash. Guessing a specific token will not reveal anything of importance beyond being able to kill the connection.
NAME That's a fair point. Let me adjust my approach: use internally comparing the stateless reset tokens. For example, we could use . Then, we would not leak anything even if both the hash lookup or the comparison of the token values (now done by comparing the values permuted by AES) are not constant-time.
OK for comparing each reset token, I think using PRP can be considered as a way of implementing constant-time comparison. Then, the discussion would be if we should state something like: "lookup of stateless reset tokens MUST NOT leak timing information. Such lookup table can for example be implemented by using PRP(statelessresettoken) instead of the stateless reset token itself, for lookup and comparing the values." Though I'm inclined to think that the text in might be fine after all.
Using a PRP like that requires touching all the values in constant time anyway, so that's just a more expensive method of doing a constant-time comparison.
I do not think that it would be expensive. Rather, I think using PRP might be the simplest and cheapest way. The hash table needs to use be resistant to hash collision DoS attacks, because the stateless reset tokens are supplied by the peers. Therefore, to calculate the hash key, I'd assume that endpoints would be using some function that takes a master secret and a stateless reset token. That function can be a PRP.
If you are using a hash table, sure. But if you can narrow the values down to those tokens that were associated with packets sent on that path, a list would be a better storage method and then a straight constant-time comparison is much simpler.
Yeah I am not sure if you would want to maintain a hash table using paths as keys just for that purpose. IIUC, at the moment, there is no need for such a hash table on the server side.
I don't think that the text here resolves the issue. I agree that you need to have a constant time comparison, but I don't believe it needs to post-date the MAC check, because the attacker knows that there is an invalid MAC. I don't object to landing the PR, but it doesn't resolve this issue.
NAME is right that the PR doesn't resolve the original issue about the ordering. NAME : is there a reason for the ordering?
Oh, I completely missed the question about ordering in there. Reading comprehension fail on my part. I've tweaked the proposed fix to account for this. Specifically: Note that I've switched this to datagram rather than packet, so match the sending and to allow for variations in how packets are assembled into datagrams. I think that fixes it, but I'll wait for input on the new changes before reattaching .
Can someone explain to a dumb guy like me why this is important? We have three paths: 1) Good packet: decrypt, validate, and then process the data in the packet. 2) Stateless reset: attempt to decrypt, fail, find a match for a registered reject token, kill connection. 3) Garbage: attempt to decrypt, fail, do not find a match, do nothing. I content that (1) and (2) are observable no matter what. If the connection goes on as in (1), further packets can be observed. If the connection stops, absence of further packets can be observed. The behavior after (3) depends on the state -- if the connection is up, further data will come out as in (1). If the connection is down, nothing will happen. What exactly is the concern?
Christian, the original text required that we only evaluate 2 after failing to match on 1 (i.e., fail to decrypt). The new text allows any order of operations. The secondary thing is that 2 needs to be constant time for each stateless reset token.
Thanks for the updated PR, NAME I think this resolves NAME concern.
Discussed in Cupertino. to be merged following consensus call.
Some small suggestions, but LG

10.4.1.  <ins> An endpoint detects a potential stateless reset using the trailing 16 bytes of the UDP datagram.  An endpoint remembers all Stateless Reset Tokens associated with the connection IDs and remote addresses for datagrams it has recently sent.  This includes Stateless Reset Tokens from NEW_CONNECTION_ID frames and the server's transport parameters but excludes Stateless Reset Tokens associated with connection IDs that are either unused or retired.  The endpoint identifies a received datagram as a stateless reset by comparing the last 16 bytes of the datagram with all Stateless Reset Tokens associated with the remote address on which the datagram was received. This comparison can be performed for every inbound datagram. Endpoints MAY skip this check if any packet from a datagram is successfully processed.  However, the comparison MUST be performed when the first packet in an incoming datagram either cannot be associated with a connection, or cannot be decrypted. An endpoint MUST NOT check for any Stateless Reset Tokens associated </ins> with connection IDs it has not used or for connection IDs that have been retired.  <ins> When comparing a datagram to Stateless Reset Token values, endpoints MUST perform the comparison without leaking information about the value of the token.  For example, performing this comparison in constant time protects the value of individual Stateless Reset Tokens from information leakage through timing side channels.  Another approach would be to store and compare the transformed values of Stateless Reset Tokens instead of the raw token values, where the transformation is defined as a cryptographically-secure pseudo-random function using a secret key (e.g., block cipher, HMAC RFC2104).  An endpoint is not expected to protect information about whether a packet was successfully decrypted, or the number of valid Stateless Reset Tokens. If the last 16 bytes of the datagram are identical in value to a </ins> Stateless Reset Token, the endpoint MUST enter the draining period  <ins> and not send any further packets on this connection. </ins> 10.4.2.


server-push for more details. Frames of unknown types (extensions), including reserved frames <del> (frame-grease) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section. </del>  The HEADERS and PUSH_PROMISE frames might reference updates to the QPACK dynamic table.  While these updates are not directly part of Frame types of the format "0x1f * N + 0x21" for integer values of N are reserved to exercise the requirement that unknown types be ignored (extensions).  These frames have no semantics, and can be <del> sent when application-layer padding is desired.  They MAY also be sent on connections where no data is currently being transferred. Endpoints MUST NOT consider these frames to have any meaning upon receipt. </del>  The payload and length of the frames are selected in any manner the implementation chooses.  8. QUIC allows the application to abruptly terminate (reset) individual extensible protocol elements.  Implementations MUST discard frames and unidirectional streams that have unknown or unsupported types. This means that any of these extension points can be safely used by <del> extensions without prior arrangement or negotiation. </del>  Extensions that could change the semantics of existing protocol components MUST be negotiated before being used.  For example, an Where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, HTTP/3 can either rely on transport-layer padding or employ the <del> reserved frame and stream types discussed in frame-grease and stream- grease.  These methods of padding produce different results in terms of the granularity of padding, the effect of packet loss and </del>  recovery, and how an implementation might control padding. 10.2.

So it turns out that there are two different things called "reserved frame types" in HTTP/3. There are the types reserved for greasing, and there are the types reserved to minimize confusion at code point overlap with HTTP/2. This PR makes three changes, separable if there's controversy: Adds "Reserved" to the frame type table, pointing to the Reserved Types section, and indicates they can be sent on all stream types. Clarifies in the Reserved section that GREASE frames can be sent on any open stream Calls out the reserved-for-H2 types as MUST NOT send, MAY error. Clarifies in the Extensibility section that, while you ignore frame types you don't know, you don't ignore that it wasn't the frame you were expecting if a particular type was required to be in that position.
We've had a long standing requirement that stream (see Section 3.2.1) by each peer By the way I interpret this, I put a check in my implementation that doesn't permit any frame until SETTINGS is received. Am I interpreting things correctly? It would be more consistent to allow reserved frame types to appear at any point on any stream that carries frames. However, I'm fine with this requirement as is but it means there is a little more effort needed to handle reserved frame types across streams.
In our implementation we are interpreting this in the same way: any frame on the control stream received before SETTINGS is a connection error. Even reserved frame types. In general the way we handle this is by discarding frame types that are forbidden rather than checking frame types that are allowed on each stream. Here is a snippet from our code. In this way we avoid the extra effort needed to handle reserved frame types that you mentioned, if I am reading your statement correctly. Basically, some frame types are explicitly disallowed. All other frame types are equal, reserved or non-reserved. Do you foresee an extension possibly benefiting from sending a frame before settings ?
Thanks NAME you made me realise I asked the question wrong. This is about reserved and non-core frames. Section 7 says: extensible protocol elements. Implementations MUST discard frames ... I have no strong use case for delivering frames before SETTINGS. But I worry it falls into a slight grey area that might affect interop. I think the correct answer is that the control stream is special case and we should clarify that in Section 7.
My preference goes to retaining the requirement as-is. This is the way H2 has worked, and I am not sure if there is a practical reason to change. FWIW, we have a similar requirement for PRIORITY frame too, which I'm also fine with: A PRIORITY frame received after other frames on a request stream MUST be treated as a connection error of type HTTPUNEXPECTEDFRAME ().
Ooh yeah, that's a good catch too. Table 1 in section 4 seems to actually highlight this behaviour with the notation. And we sort of have some text that captures this in section 4: We could add tweaks or further guidance to this paragraph ( in a PR that also that makes things explicitly clear .
On receipt, reserved is irrelevant -- it's just an unknown frame type. The reservation is simply to permit senders to generate unknown frame types without accidental collision to a real extension the client might support. So the question is whether "ignoring" unknown frame types means pretending that they weren't even there, and therefore the subsequent frame might still be the first frame on the stream. I'm inclined to say no -- you take no action on a frame you don't recognize, but that doesn't mean that the next frame was the first frame.
I think the overview table in HTTP/3 section 4 should probably also include reserved frame types. This will make it clear what streams these frames can appear on.

server-push for more details. Frames of unknown types (extensions), including reserved frames  <ins> (frame-reserved) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section. </ins> The HEADERS and PUSH_PROMISE frames might reference updates to the QPACK dynamic table.  While these updates are not directly part of Frame types of the format "0x1f * N + 0x21" for integer values of N are reserved to exercise the requirement that unknown types be ignored (extensions).  These frames have no semantics, and can be  <ins> sent on any open stream when application-layer padding is desired. They MAY also be sent on connections where no data is currently being transferred.  Endpoints MUST NOT consider these frames to have any meaning upon receipt. </ins> The payload and length of the frames are selected in any manner the implementation chooses. <ins> Frame types which were used in HTTP/2 where there is no corresponding HTTP/3 frame have also been reserved (iana-frames).  These frame types MUST NOT be sent, and receipt MAY be treated as an error of type HTTP_UNEXPECTED_FRAME. </ins> 8. QUIC allows the application to abruptly terminate (reset) individual extensible protocol elements.  Implementations MUST discard frames and unidirectional streams that have unknown or unsupported types. This means that any of these extension points can be safely used by  <ins> extensions without prior arrangement or negotiation.  However, where a known frame type is required to be in a specific location, such as the SETTINGS frame as the first frame of the control stream (see control-streams), an unknown frame type does not satisfy that requirement and SHOULD be treated as an error. </ins> Extensions that could change the semantics of existing protocol components MUST be negotiated before being used.  For example, an Where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, HTTP/3 can either rely on transport-layer padding or employ the  <ins> reserved frame and stream types discussed in frame-reserved and stream-grease.  These methods of padding produce different results in terms of the granularity of padding, the effect of packet loss and </ins> recovery, and how an implementation might control padding. 10.2.


described in more detail in QUIC-TRANSPORT.  An endpoint MAY choose to treat a stream error as a connection error.  This section describes HTTP/3-specific error codes which can be used to express the cause of a connection or stream error.

by saying that, since error codes are extensible, you can't treat the receipt of an error code you don't understand or don't think you should have gotten as an error itself. (Though it can still be an error that the stream closed, regardless of the error code.) (Which leads to the question, should we be greasing the error code space?)
Yes! I would create a grease stream and it could be closed (stop sending and/or reset) by peers.
That seems to have very narrow applicability. Would you only be permitted to send a greasing error code in that case? Or to put the question differently, what would happen if you decided to randomly turn a subset of the semantic-ful error codes you generate into greasing codes?
Per the language of this PR, I think that would be fine. Possibly with the exception of the errors that are supposed to inform behavior, like REJECTED and perhaps EARLYRESPONSE.
The HTTP/3 spec is a little light on detail about processing an error code that should have never been sent. At NAME suggestion, this issue is scoped to discussion of HTTP/3 stream errors: Example a) a client receives a STOPSENDING with HTTPREQUESTCANCELLED Example b) a server receives a RESETSTREAM with HTTPLIMITEXCEEDED
I'm mostly inclined to treat this with the same fatalism as an invalid code in an APPLICATIONCLOSE code that makes no sense. The stream is gone, regardless of the error code. If it was a critical stream, then CLOSEDCRITICALSTREAM; otherwise, perhaps a MAY GENERALPROTOCOL_ERROR?
I agree with NAME The error code doesn't matter. What matters is how you handle it. Error codes with no specific handling are fine; error codes with special handling can be ignored. The question of what to do with critical streams is separable. Don't make things worse by coupling the two concepts.
Oh, totally separable. But then you'd then have two competing permissions-to-blow up -- because the critical stream close (regardless of error code) and because a stream closed with an invalid error code (regardless of which stream). And I don't think it matters which reason you have to blow up or what the resulting error is.

described in more detail in QUIC-TRANSPORT.  An endpoint MAY choose to treat a stream error as a connection error. <ins> Because new error codes can be defined without negotiation (see extensions), receipt of an unknown error code or use of an error code in an unexpected context MUST NOT be treated as an error.  However, closing a stream can constitute an error regardless of the error code (see request-response). </ins> This section describes HTTP/3-specific error codes which can be used to express the cause of a connection or stream error.


with some text in the TBD Security Considerations section.
If I understand correctly, the decoder stream buffer which endpoint has to keep grows indefinitely if encoder-side app does not return back flow control credit. The number of Header Acknowledgement instruction is bounded by the number of max blocked streams which decoder can specify. On the other hand, the number of Stream Cancellation is not bounded. So if encoder-side app keeps resetting stream without sending MAXSTREAMDATA, decoder-side has to keep all closed stream in decoder stream. I'm not sure this has been discussed already.
The solution to this seems implementation-dependent, but implementations should place a bound on the amount of data waiting to be sent on the decoder stream.
Hypothetically, this limit would create a way to force the peer to kill the connection by starving the decoder stream of flow control credit and then cancelling lots of streams. However, the consumer of (and therefore the entity which could fail to read) the decoder stream is the encoder, and the encoder can trivially cause the connection to close by emitting an invalid instruction anyway. That seems fine.
NAME writes: This is incorrect: an for each header that references the dynamic table.

TBD. <ins> While the negotiated limit on the dynamic table size accounts for much of the memory that can be consumed by a QPACK implementation, data which cannot be immediately sent due to flow control is not affected by this limit.  Implementations should limit the size of unsent data, especially on the decoder stream where flexibility to choose what to send is limited.  Possible responses to an excess of unsent data might include limiting the ability of the peer to open new streams, reading only from the encoder stream, or closing the connection. </ins> 8. 8.1.


9.3. <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 general security of this mechanism can be shown in the general case. The AEAD algorithms described in this document are assumed to be PRFs. The header protection algorithms defined in this document take the form: This construction is secure against chosen plaintext attacks (IND- CPA) IMC. </del>  Use of the same key and ciphertext sample more than once risks compromising header protection.  Protecting two different headers

This paper studies several "nonce hiding" transformations, of which the QUIC header protection algorithm is one. This change replaces the old analysis text with something a bit more crisp.
I'm really happy to see good research on this that confirms our intuitions about one of the most important QUIC-specific constructions. Thanks for doing this Chris. Are you planning to make a similar contribution to DTLS 1.3?
My pleasure. :-) Yep!

9.3.  <ins> NAN analyzes authenticated encryption algorithms which provide nonce privacy, referred to as "Hide Nonce" (HN) transforms.  The general header protection construction in this document is one of those algorithms (HN1).  Header protection uses the output of the packet protection AEAD to derive "sample", and then encrypts the header field using a pseudorandom function (PRF) as follows: The header protection variants in this document use a pseudorandom permutation (PRP) in place of a generic PRF.  However, since all PRPs are also PRFs IMC, these variants do not deviate from the HN1 construction. As "hp_key" is distinct from the packet protection key, it follows that header protection achieves AE2 security as defined in NAN and therefore guarantees privacy of "field", the protected packet header. Future header protection variants based on this construction MUST use a PRF to ensure equivalent security guarantees. </ins> Use of the same key and ciphertext sample more than once risks compromising header protection.  Protecting two different headers


at any encryption level, whereas those associated with transferring data can only appear in the 0-RTT and 1-RTT encryption levels: <del> PADDING frames MAY appear in packets of any encryption level. </del>  CRYPTO and CONNECTION_CLOSE frames MAY appear in packets of any encryption level except 0-RTT.

This is an alternative to .
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.
This may duplicate a previous issue, but there has been a lot of aggressive closing. Instead of using "quic hp", etc. we should use a label that reflects the QUIC version. Assuming I am counting correctly, we can do: "qxxxxxxxx hp" where xx is the version in hex. This would match TLS practice.
My expectation was that we would have "quic" and then "quic2", etc... Maybe that doesn't have the effect that is desired, because it's less obvious, but I prefer to proceed as though this were the only version.
Martin's suggested plan SGTM.
I don't strongly object, but it seems like if we know this is v1, we might want to indicate that, but ultimately I agree it's a matter of taste
Tokyo conclusion: NAME and NAME will sort this out.
London discussion: NAME and NAME to resume work
If the expansions are revised, could we get rid of spaces then?
I don't think that would be advisable. We should keep this as close to TLS practice as possible.
Discussed in Montreal, close with no action
Looks good to me. I think PING should be allowed everywhere.

at any encryption level, whereas those associated with transferring data can only appear in the 0-RTT and 1-RTT encryption levels:  <ins> PADDING and PING frames MAY appear in packets of any encryption level. </ins> CRYPTO and CONNECTION_CLOSE frames MAY appear in packets of any encryption level except 0-RTT.


retiring-cids. Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number <del> greater than any previously sent to the peer MAY be treated as a </del>  connection error of type PROTOCOL_VIOLATION. The sequence number specified in a RETIRE_CONNECTION_ID frame MUST

When it comes to error checks for protocol violations, our principle is that easy to implement error checks are REQUIRED. Only error checks that might require a lot of state to perform are optional. The check that the sequence number retired in a RETIRECONNECTIONID frame is smaller than the highest issued connection ID is a single trivial uint64 comparison. Furthermore, every implementation needs to track the highest issued sequence number anyway, since connection IDs are issued in order.
This is small and I support it, but I think it needs to be marked design and run through the process because it changes normative language.
An issue to match with PR The text is: "Receipt of a RETIRECONNECTIONID frame containing a sequence number greater than any previously sent to the peer MAY(->MUST) be treated as a connection error of type PROTOCOL_VIOLATION."
Just pasting in the PR text:
I agree with the reasoning here and agree that it’s easy enough to check, but didn’t we consider this previously and decide against it for some reason (reordering or some other issue making this possible to have happen?)?
Reordering won't change what the maximum sequence number is. That should be tracked.
I agree with Martin(and Marten). This is similar to the rule that a peer can't ACK a packet number you've never sent.
NAME : I have a vague recollection, but I think that might have been about tracking this at the receiver of NEWCONNECTIONID frames. This issue is about what the corresponding sender does when the receiver indicates a CID that the sender never sent.
I think there might be issues with checking for any invalid sequence number, but the maximum should be easy. But if that is the case, what is the point of specifically checking for max and not a host of other bad cases? Sanity check...
I think the question is if we consider processing of NEWCONNECTIONID and RETIRECONNECTIONID frames as a mandatory feature. Migration is an optional feature of QUIC. For endpoints that do not migrate, there is no reason to update the Connection IDs (note that even though we recently introduced Retire Prior To field, we do not mandate endpoints to respect it - the keyword is "SHOULD"). If we consider that way, I think it is reasonable to never use a MUST for anything related to NEWCONNECTIONID or RETIRECONNECTIONID frames (except for parsing and skipping them).
NAME I wouldn't encourage people to think of NEWCONNECTIONID and RETIRECONNECTIONID as optional, but I agree that effectively they are in many cases. However, if you sent NEWCONNECTIONID, I don't think it's optional to not correctly process RETIRECONNECTIONID, and that's the question here(I copy-pasted the paragraph above). If the peer never sends RETIRECONNECTIONID because it doesn't fully implement connection ID functionality, then this is not a problem.
I agree with Ian. Processing these frames, and acting accordingly is not optional. Even in the absence of migration RETIRECONNECTIONID can be used to force a peer to start using new connection IDs. WinQuic will make use of this to dynamically balance load of connections across processors; independent of migration.
NAME Thank you for clarifying your position. I'd be perfectly fine if the intent of the proposed change is to require validation if the endpoint has sent a NEWCONNECTIONID frame. My problem has been that I have been uncertain if the proposed change meant that; I'd be happy to proposed editorial tweaks to better clarify that. NAME By saying "force a peer to start using new connection IDs", I think you are referring to the Retire Prior To field of the NEWCONNECTIONID frame. That's a SHOULD (as the text currently says), IIRC we have agreed on that.
If you've never sent a NEWCONNECTIONID, how could receiving a RETIRECONNECTIONID frame ever be valid?
NAME sorry. You're right, I meant the NEWCONNECTIONID frame's Retire Prior To field. But I disagree on your interpretation of the : The is for . If you don't do it, the connection will eventually break, likely via a stateless reset.
NAME The preferredaddress TP. An endpoint is allowed, even though not required, to send a RETIRECONNECTIONID frame to retire a CID used during handshake. Quoting from : An endpoint sends a RETIRECONNECTIONID frame (type=0x19) to indicate that it will no longer use a connection ID that was issued by its peer. This may include the connection ID provided during the handshake._
NAME The question is the intent of using "SHOULD in a timely manner" rather than "MUST in a timely manner". Yes, the connection would break, if the receiver does not migrate to a new connection ID. However, because we use a SHOULD, that is one of the expected outcomes when an endpoint sends a NEWCONNECTIONID frame carrying non-zero Retire Prior To field.
NAME it sounds like you're arguing that it's an acceptable trade off for clients that don't want to implement support for the Retire Prior To field. I hope that isn't what you're saying. As it is, I'm thinking I'm going to have to add some greasing for this feature to WinQuic.
NAME I am saying that that is the natural way of reading the text. It seems to me that there is a disagreement on what the text implies, and I think it'd be more productive to try to polish up the intent of the text before resorting to encouraging particular behavior through greasing; hence .
Thanks NAME that sounds about right, this is the other direction, makes sense.

retiring-cids. Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number  <ins> greater than any previously sent to the peer MUST be treated as a </ins> connection error of type PROTOCOL_VIOLATION. The sequence number specified in a RETIRE_CONNECTION_ID frame MUST


packet in the datagram. Every QUIC packet that is coalesced into a single UDP datagram is <del> separate and complete.  Though the values of some fields in the packet header might be redundant, no fields are omitted.  The receiver of coalesced QUIC packets MUST individually process each QUIC packet and separately acknowledge them, as if they were received as the payload of different UDP datagrams.  For example, if decryption fails (because the keys are not available or any other reason), the receiver MAY either discard or buffer the packet for later processing and MUST attempt to process the remaining packets. </del>  Retry packets (packet-retry), Version Negotiation packets (packet- version), and packets with a short header (short-header) do not

At the top of page 66 of the , we read: The second sentence seems to imply that a field could be omitted. Does this sentence sound odd to anyone else?
While it is perhaps technically redundant, I think it is actually quite helpful. The first sentence says, "complete" but that might be open to interpretation. The second sentence makes it clear what that means. I like it. YMMV :)
My issue is not with the redundancy of the second senteance, but rather with its meaning. The specification that "no fields be omitted" implies that fields could be omitted -- but they can't be: There are no optional fields in packet headers.
Maybe change the second sentence to something like:
NAME writes: The third sentence clarifies the first sentence well, too. I would just drop the second sentence altogether.
I can't remember now, but we did have an intermediate design where redundant fields were absent. This sentence may have crept in around then. I agree with NAME : it does seem redundant now.

packet in the datagram. Every QUIC packet that is coalesced into a single UDP datagram is  <ins> separate and complete.  The receiver of coalesced QUIC packets MUST individually process each QUIC packet and separately acknowledge them, as if they were received as the payload of different UDP datagrams.  For example, if decryption fails (because the keys are not available or any other reason), the receiver MAY either discard or buffer the packet for later processing and MUST attempt to process the remaining packets. </ins> Retry packets (packet-retry), Version Negotiation packets (packet- version), and packets with a short header (short-header) do not