subjectAltName field of the certificate (see RFC6125).  For a host that is an IP address, the client MUST verify that the address appears as an iPAddress in the subjectAltName field of the <del> certificate. </del>  Clients SHOULD NOT open more than one HTTP/3 connection to a given host and port pair, where the host is derived from a URI, a selected

Addresses NAME comments on . While this adds a new MUST NOT, I believe it's editorial because it's simply stating the implicit result of the "MUST verify" in that PR. If anyone disagrees, speak up and I'll change it. Separately, I think there's a case for this "MUST NOT" to be a "SHOULD NOT," as we all know there are situations in which clients proceed through a certificate warning. It's a reasonable semantic distinction whether the client does not consider the server authoritative but processes the result anyway (MUST NOT), or considers the server authoritative because the user told it to (SHOULD NOT). Opinions on the bikeshed welcome.

subjectAltName field of the certificate (see RFC6125).  For a host that is an IP address, the client MUST verify that the address appears as an iPAddress in the subjectAltName field of the  <ins> certificate.  If the hostname or address is not present in the certificate, the client MUST NOT consider the server authoritative for origins containing that hostname or address.  See Section 5.4 of SEMANTICS for more detail on authoritative access. </ins> Clients SHOULD NOT open more than one HTTP/3 connection to a given host and port pair, where the host is derived from a URI, a selected


idempotent action. A simplified TLS handshake with 0-RTT application data is shown in <del> tls-full.  Note that this omits the EndOfEarlyData message, which is not used in QUIC (see remove-eoed).  Likewise, neither ChangeCipherSpec nor KeyUpdate messages are used by QUIC; ChangeCipherSpec is redundant in TLS 1.3 and QUIC has defined its own key update mechanism key-update. </del>  Data is protected using a number of encryption levels: As a result, EndOfEarlyData does not appear in the TLS handshake transcript.  9. All of the security considerations that apply to TLS also apply to

Should we use a more specific error code than PROTOCOL_VIOLATION? Something in the 0x1XX range?
I couldn't see something more applicable. You could fall back to a generic TLS alert, like handshake_failure, but the error is really a QUIC one.
NAME As NAME says, our code that emits ChangeCipherSpec would check if the underlying protocol is the TLS record protocol. If that's something else, a TLS alert is generated. See URL NAME I'm not sure if I share that view. We already have certain requirements on how TLS is used, and we use TLS error codes to communicate errors (e.g., use of unexpectedmessage when a TLS Key Update message is received). FWIW, the code above in picotls sends unexpectedmessage (I can change that TLS error code for sure, I just merged it).
That seems fine, but in my view the fault was with the ClientHello, not the CCS.
I agree that the fault is with ClientHello, it's merely a coincidence that we detect that problem when trying to build particular portion of the response. In case of picotls, return of this error will be propagated to the application alongside a partially built buffer, so QUIC stacks using picotls would just send the TLS error code.
I observe that implementation of this PR is causing interop failures during tests. Some implementations have been updated, some have not been. I recently updated Picoquic to use the latest version of Picotls, and the results are mixed: Handshake with Quant and Quickly that were failing before the update are now succeeding; handshakes with Quiche that were succeeding are now failing. This is clearly a case of spec confusion, with some stacks rushing to test this PR and some sticking to the stated spec. How about clearing that confusion, updating the spec and releasing draft-28?
NAME that is the plan as soon as the chairs declare consensus.
Yep. The consensus call for this ends tomorrow and there seems to be no push back so far...
This came up in interop testing. This mode causes the TLS stack to generate a ChangeCipherSpec message, which caused our server to crash. That's a bug on our end, but it's still a problem. We need to decide whether this mode is acceptable. Right now, we only say that it is "redundant". A prohibition this mode would be easier all around. It would avoid spending 32 bytes for both endpoints too. Unfortunately, this is a design change.
I'm not sure what this change would get us. Isn't it simpler to ignore CCS on receipt instead of requiring the receiver to check for CCS and fail the handshake?
So yes, one answer is to ignore it and move on. But we should say that and require that implementations do so. The answer I prefer is prohibition. This is wasted bytes.
If this were purely a QUIC matter, I would agree. This one is slightly trickier because it puts additional requirements on TLS stacks and not all QUIC implementers control their TLS stack. Additionally, is this a principled approach? Should we also be banning TLS padding (RFC7685) because it is similarly redundant?
Practical question: Is there anyone for whom this restriction would be onerous? Ideally there would be a day we'd be able to get rid of compatibility mode for TLS 1.3 entirely, so prohibiting it from QUIC seems like the first step. As a corollary, it seems like TLS implementations should have a knob to enable/disable it, otherwise compatibility mode becomes the de-facto only mode.
Our client has a switch that defaults to off. Apparently picotls had no switch, but NAME already .
Just to be clear, picotls has had the switch, but it has been sending a 32-byte legacysessionid even when the switch was off. That was a violation of a MUST in RFC 8446, purely implementation bug. NAME In QUIC, it is impossible to send CCS, because the CRYPTO streams of QUIC can only carry handshake messages. CSS is not a handshake message, it's a TLS record. Therefore, it is my understanding that prohibiting the client from using Compatibility Mode is an editorial clarification. PS. This comment is about forbidding use of compatibility mode on the client-side, I have some concerns about making a design change that a MUST for servers sending PROTOCOLVIOLATION in response. Please see URL
Just to close the loop (and discuss on the issue): Having a SHOULD on the server reaction is fine with me.
Sounds like design?
the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Here is the mailing list thread: URL I believe the consensus is to make it a conn error; PR to follow. the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Should this be a connection error, or am I missing something?

idempotent action. A simplified TLS handshake with 0-RTT application data is shown in  <ins> tls-full. tls-full omits the EndOfEarlyData message, which is not used in QUIC; see remove-eoed.  Likewise, neither ChangeCipherSpec nor KeyUpdate messages are used by QUIC.  ChangeCipherSpec is redundant in TLS 1.3; see compat-mode.  QUIC has its own key update mechanism; see key- update. </ins> Data is protected using a number of encryption levels: As a result, EndOfEarlyData does not appear in the TLS handshake transcript. <ins> 8.4. Appendix D.4 of TLS13 describes an alteration to the TLS 1.3 handshake as a workaround for bugs in some middleboxes.  The TLS 1.3 middlebox compatibility mode involves setting the legacy_session_id field to a 32-byte value in the ClientHello and ServerHello, then sending a change_cipher_spec record.  Both field and record carry no semantic content and are ignored. This mode has no use in QUIC as it only applies to middleboxes that interfere with TLS over TCP.  QUIC also provides no means to carry a change_cipher_spec record.  A client MUST NOT request the use of the TLS 1.3 compatibility mode.  A server SHOULD treat the receipt of a TLS ClientHello that with a non-empty legacy_session_id field as a connection error of type PROTOCOL_VIOLATION. </ins> 9. All of the security considerations that apply to TLS also apply to


In addition to sending data in the packet number space for which the timer expired, the sender SHOULD send ack-eliciting packets from other packet number spaces with in-flight data, coalescing packets if <del> possible. </del>  If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay.

Sending in multiple PN spaces on PTO is particularly useful when the peer only has receive keys for 1 pn space, and the you don't know which.
(deleted comment, because the initially mentioned problem was incorrect. the requirement to arm the PTO timer after the handshake finishes is in Section 5.2.2)
There's some architectural inconsistency around the treatment of 1RTT PTOs at the end of the handshake. On the one hand, we are not supposed to set the PTO timer for 1RTT before the handshake is complete. This is to prevent us from retransmitting stuff the peer may not be able to process. On the other hand, we are supposed to arm the PTO timer when the handshake is confirmed. This is the point where we're guaranteed that the peer has receive keys. So there is a weird gap between handshake complete and handshake confirmed where new 1RTT packets will arm the PTO, but outstanding 0RTT or 1RTT packets from before handshake complete will not have an active PTO timer. This isn't the end of the world -- the connection will recover -- but it doesn't make any sense, and could result in either excessive PTO delays, or 1RTT probes that the peer can't decode.
NAME can we get a status update?
The PTO is always armed when there are bytes in flight(except when the loss timer is armed), it's just a matter of what PN spaces it can be armed for at a given point in time. Once the connection is handshake complete, it's immediately allowed to arm it for inflight 1-RTT ack-eliciting packets. Re-reading the text, I couldn't find anything explicitly calling out that the alarm may need to be recomputed and reset in the handshake confirmed case, though it is stated for discarding keys. As clarified in when the client is handshake complete but not confirmed, it is unsure whether the server has Handshake keys or ApplicationData keys, but it's very unlikely to have both. As such, sending something ack-eliciting from both PN spaces is safest.
The packet number space that endpoint should send probe packet is determined by GetEarliestTimeAndSpace function. It chooses packet number space where the latest packet has earliest dispatch time. Suppose client sent 2 packets: Handshake(clienthandshake) and Short packet(clientshort). Somehow it didn't get any ACK for them, on first PTO timeout, it resent Handshake (client_handshake). This makes Short packet number space becomes the earliest packet number space and next PTO timeout, client sent Short probe packet assuming that IsHandshakeCompleted() means handshake completion not handshake confirmation. I think it should instead send Handshake probe packet in this case. We have unified PTO timer and sending probe in alternate manner is a bit strange.
I agree that this is weird. It's worse than you describe because the exponent on the intervals between Handshake packets is 4, not 2. We send probes (just a PING) when probing the short packet as recommended, so it isn't too, too bad, but it is also not that great. I looked into this in mozilla/neqo if people want more context. I didn't have the time to think through what the ideal solution might be.
As discussed in neqo issue thread, which packet number spaces should be chosen depends on the situation. ngtcp2 sends Initial and Handshake probes at the same time if server sees they are in-flight when PTO timer fires so that it can coalesce them and also handle the situation that client might drop Initial key. This is a deviation from the draft-27. For the particular situation that this issue describes, I think client can just send Handshake probe packet. If server still has Handshake key, it will respond with Handshake packet. If not, then it will send HANDSHAKE_DONE frame in Short packet, and client confirms handshake completion. Sending Short probe with Handshake packet might reduce the time before client reacts the lost Short packet, but might be discarded if server does not have appropriate key.
In this case, I believe alternating is the right thing to do, because the server either has Handshake receive keys or 1-RTT receive keys, and the client doesn't know which. Because of HANDSHAKEDONE, the client will eventually complete the handshake and drop the Handshake PN space as long as the client's Handshake packet(containing the finished) gets to the server. However, this case is an excellent motivation for this text: This text was written prior to HANDSHAKEDONE, but I think there may be other reasons to use handshake complete and not confirmed in this case, so I would be very hesitant to change it without a lot of consideration. Is there a part of mozilla/neqo I should focus on? The text talks a lot about packets being marked as lost when the PTO timer fires, which makes me very confused, since QUIC doesn't mark packets as lost when the PTO fires.
Leaving aside that recommendation for the moment (which I agree is close to mandatory), the experience is pretty poor if there is only one key. You just moved to a 4x exponent on the PTO timer rather than a 2x timer. I guess that's why we have the recommendation. (For reference, neqo tells the frame sending apparatus that it lost some data when a PTO timer fires. That causes those older frames to be sent again rather than trying to find fresh data to send. That's on the principle that older data is more important. But that isn't relevant to this discussion. The main comment to look at is , which shows how the interleaving can be a little janky.) I realize that we haven't reconciled this with the requirement not to arm the application data PTO when there are handshake/initial packets outstanding. That might limit this to the one case of interleaving Initial and Handshake.
Yes, you move to 4x instead of 2x, but it's worth understanding what packets need to be lost for the 2x vs 4x to come into play. 1) The client's Handshake packet is lost twice in a row. Not that unlikely, but it's known the Client Finished blocks the server from processing 1-RTT packets, so I think there's an understanding of its importance. 2) (case from this issue) The client's Handshake packet is delivered, but the ACK is lost so the client retransmits it then that packet is lost AND both the first HANDSHAKEDONE packet and its retransmission are lost. I'm sure there are many other permutations of these, but the first case seems a lot more likely than the second case. In this case, the HANDSHAKEDONE does a nice job of mitigating some potential issues.
LGTM

In addition to sending data in the packet number space for which the timer expired, the sender SHOULD send ack-eliciting packets from other packet number spaces with in-flight data, coalescing packets if  <ins> possible.  This is particularly valuable when the server has both Initial and Handshake data in-flight or the client has both Handshake and ApplicationData in-flight, because the peer might only have receive keys for one of the two packet number spaces. </ins> If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay.


6.3. For a server to use a new version in the future, clients need to <del> correctly handle unsupported versions.  To help ensure this, a server SHOULD include a version that is reserved for forcing version negotiation (0x?a?a?a?a as defined in versions) when generating a Version Negotiation packet. The design of version negotiation permits a server to avoid maintaining state for packets that it rejects in this fashion. A client MAY send a packet using a version that is reserved for forcing version negotiation.  This can be used to solicit a list of supported versions from a server. </del>  7.

This section was a holdover from when we had real version negotiation, so it assumed a bunch of actions that don't really apply in the current document. This doesn't change what is permitted, but it reframes it a little.
Section 6.3 states: I think that the text might be stale, as we no longer specify how a client should handle the payload of the Version Negotiation packet. All what we specify now is that when a client receives a Version Negotiation packet, it MUST abandon the current connection attempt.
This is still possible, it just doesn't have any effect on a version 1 client. I think that we can leave this as-is without ill effect.
I'm not sure, as the client is required to "abandon the current connection attempt" () when receiving a Version Negotiation packet, even without consulting the payload of that packet. Doesn't that prohibit the servers from sending VN packets against V1 packets, assuming that servers would prefer serving all the connections that they can accept?
We may want to change the text to say that these versions are reserved for future greasing, but yeah, I think the text needs some rework.
This feels a bit editorial -- anyone have an issue with that?
I think we have two ways forward: Drop the idea of version number greasing using 0x?a?a?a?a. Allow the servers to send Version Negotiation packets. We would need to require clients to handle it, rather than requiring them to "abandon the current connection attempt." I think it would be a design issue if we decide to pick up the latter.
Presumably clients could still use greasing version numbers, so I don't think that we necessarily need to do anything other than an editorial tweak.
Apologies, I was confused when I wrote my last comment. Yes, this can be an editorial tweak, that allows the client to respect the Version Negotiation packet, when it sends an Initial packet with a greasing version number. Or, we can leave the text as-is, claiming that the act of sending a long header packet using a greasing version number is solely governed by the invariants I-D rather than the V1 I-D.
Really, governed by whatever I-D eventually defines our version negotiation mechanism.
NAME you seem to have an idea of what to do here, can you maybe propose text? I'm not sure if we need text, but I'd be happy to look at some.
Thank you for working on this. Looks good to me.

6.3. For a server to use a new version in the future, clients need to  <ins> correctly handle unsupported versions.  Some version numbers (0x?a?a?a?a as defined in versions) are reserved for inclusion in fields that contain version numbers. Endpoints MAY add reserved versions to any field where unknown or unsupported versions are ignored to test that a peer correctly ignores the value.  For instance, an endpoint could include a reserved version in a Version Negotiation packet; see packet-version. Endpoints MAY send packets with a reserved version to test that a peer correctly discards the packet. </ins> 7.


stop using the corresponding connection IDs and retire them with RETIRE_CONNECTION_ID frames before adding the newly provided connection ID to the set of active connection IDs.  This ordering <del> allows an endpoint that has already supplied its peer with as many connection IDs as allowed by the active_connection_id_limit transport parameter to replace those connection IDs with new ones as necessary. Failure to cease using the connection IDs when requested can result in connection failures, as the issuing endpoint might be unable to </del>  continue using the connection IDs with the active connection. 5.2.

We already had something here, but I think that it missed the central reason, which is the combination of: wanting to replace all connection IDs having used the limit from activeconnectionid_limit wanting to ensure that there is >= 1 available connection IDs left This phrasing isn't quite that explicit, but I think that it is clear enough.
It is not obvious from this, but the intent here is to allow NEWCONNECTIONID to exceed the limit from activeconnectionid_limit.
Nice wording, looks good!

stop using the corresponding connection IDs and retire them with RETIRE_CONNECTION_ID frames before adding the newly provided connection ID to the set of active connection IDs.  This ordering  <ins> allows an endpoint to replace all active connection IDs without the possibility of a peer having no available connection IDs and without exceeding the limit the peer sets in the active_connection_id_limit transport parameter; see transport-parameter-definitions.  Failure to cease using the connection IDs when requested can result in connection failures, as the issuing endpoint might be unable to </ins> continue using the connection IDs with the active connection. 5.2.


If TLS experiences an error, it generates an appropriate alert as defined in Section 6 of TLS13. <del> A TLS alert is turned into a QUIC connection error by converting the one-byte alert description into a QUIC error code.  The alert </del>  description is added to 0x100 to produce a QUIC error code from the range reserved for CRYPTO_ERROR.  The resulting value is sent in a QUIC CONNECTION_CLOSE frame of type 0x1c. The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level.  4.11. After QUIC moves to a new encryption level, packet protection keys protocol negotiation.  TLS uses Application Layer Protocol Negotiation (ALPN) ALPN to select an application protocol.  Unless another mechanism is used for agreeing on an application protocol, <del> endpoints MUST use ALPN for this purpose.  When using ALPN, endpoints MUST immediately close a connection (see Section 10.3 in QUIC- TRANSPORT) if an application protocol is not negotiated with a no_application_protocol TLS alert (QUIC error code 0x178, see tls- errors).  While ALPN only specifies that servers use this alert, QUIC clients MUST also use it to terminate a connection when ALPN negotiation fails. </del>  An application protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol compatible

This was already permitted by the transport document, but it was not adequately explained. The example that tripped us up was ALPN, which has received a bit of attention here.
Sec 8.1. of the quic-tls draft states that, if an application protocol is not negotiated, endpoints MUST immediately terminate the connection with a noapplicationprotocol TLS alert (QUIC error code 0x178). For additional security, some TLS implementations choose to hide the exact reason for a handshake failure, and only issue a handshakefailure alert instead. The QUIC layer obtaining the "true alert" may be difficult or impossible. Maybe the requirement to send noapplication_protocol should be a SHOULD instead.
This is probably fine as the reason is observable. It is true that some TLS alerts are hidden, but that is generally because that leaks private information. But here the information is public, or at least shared by client and server. Is the concern that this is potentially sent in an Initial packet? Because that shouldn't be necessary, or even possible.
I'm not concerned with the security implications, or lack thereof, of sending noapplicationprotocol. My main concern is that we, and other implementations, will be non-compliant with the draft because we have no choice but to send a general handshakefailure alert in this case. Our TLS implementation doesn't allow us to choose which alerts are converted to handshakefailure before being reported to QUIC; it's all or nothing.
I'm not sure that I'm following this: you are sending a QUIC CONNECTIONCLOSE frame, not a TLS alert. Our stack makes the appropriate CONNECTIONCLOSE directly without talking to TLS.
I'm referring to the error code field in the QUIC CONNECTIONCLOSE frame. In the case of a crypto error, the QUIC error code for handshake failure is 0x128. The QUIC error code for no application protocol is 0x178. The draft mandates that we send 0x178. In our stack, TLS does the ALPN parsing and if there is a TLS alert, QUIC converts it to a CONNECTIONCLOSE with the reported error code; TLS alert 0x28 for handshakefailure, 0x78 for noapplicationprotocol. Due to TLS converting all alerts during the handshake to handshakefailure, we end up sending a QUIC CONNECTION_CLOSE frame with 0x128, instead of 0x178.
I believe what you are asking is already catered for. The requirement for the failure case is MUST close, and the most appropriate error code is defined.
To follow up with a way to resolve this. I don't believe we need to change any normative language and so this is not a design issue. I'll leave it to editors' discretion on whether there is any editorial work required to address the original concern.
I'm still stuck on how the TLS code needs to be involved in this. Our code generates the alert, but does so .
NAME I think the problem is due to how certain implementations handle ALPN. In case of H2O, we handle ALPN in our onclienthello callback, that is invoked by picotls (URL). At this point, the call stack is H2O (application) -> quicly (QUIC stack) -> picotls (TLS stack) -> onclienthello (application callback). In this type of design, if the TLS stack is going to determine the error code (or change the error code returned from a callback), then it would be impossible (or difficult) for the QUIC stack to send the appropriate code.
I see. I hope that the application isn't the callback code, but that quicly is involved in this somehow; otherwise, you have just made H2O responsible for fulfilling QUIC requirements that you might assume quicly would be responsible for. In general, I would assume that you have access to QUIC transport state in your callback from the TLS stack. Let's say that you choke on the ALPN. You cause TLS to issue a generic alert (which is fine, as Lucas points out). At some later point that alert is going to hit QUIC code again so that it can be translated to a QUIC alert. If you can save the reason from the callback (presumably you need this capability to save transport parameters) then you can construct the right message. Of course, that's complicated. The generic alert is fine.
FWIW this particular error code is the one I've seen the most of during interop. So I will make the observation that anyone building a stack that causes an ALPN failure to not be exposed, is going to cause a lot of problems for future QUIC as a generic transport layer.
NAME Yeah I think my preference goes to not having an abstraction layer in our QUIC protocol stack for hiding TLS handshake protocol API. I'd argue that the intent of using TLS in QUIC is to reuse TLS including its extensions when possible. Requiring (or recommending) a QUIC stack to provide abstraction for those extensions (that happen at the handshake protocol level) reduces that benefit. That said, I think we agree that in this particular case a generic alert is fine, and that what we need at most is an editorial improvement.
I agree that the normative requirements are fine. It would be good to expose the specific ALPN error, and we can make the necessary TLS change to detect this properly. However, "generic TLS alerts" are a user-configurable setting, and we're reluctant to just override customer preference just because it's in a CONNECTION_CLOSE instead of a TLS Alert. Moreover, when the TLS WG gets around to encrypting the ALPN, it is not clear that exposing this error is so benign anymore.

Moreover, when the TLS WG gets around to encrypting the ALPN, it is not clear that exposing this error is so benign anymore. Will ESNI/ECHO define a way to protect the confidentiality of such TLS alerts?
Not with the current designs, but we've been discussing encrypting more, in which case maybe?

If TLS experiences an error, it generates an appropriate alert as defined in Section 6 of TLS13.  <ins> A TLS alert is converted into a QUIC connection error.  The alert </ins> description is added to 0x100 to produce a QUIC error code from the range reserved for CRYPTO_ERROR.  The resulting value is sent in a QUIC CONNECTION_CLOSE frame of type 0x1c. The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level. <ins> QUIC permits the use of a generic code in place of a specific error code; see Section 11 of QUIC-TRANSPORT.  For TLS alerts, this includes replacing any alert with a generic alert, such as handshake_failure (0x128 in QUIC).  Endpoints MAY use a generic error code to avoid possibly exposing confidential information. </ins> 4.11. After QUIC moves to a new encryption level, packet protection keys protocol negotiation.  TLS uses Application Layer Protocol Negotiation (ALPN) ALPN to select an application protocol.  Unless another mechanism is used for agreeing on an application protocol,  <ins> endpoints MUST use ALPN for this purpose. When using ALPN, endpoints MUST immediately close a connection (see Section 10.3 of QUIC-TRANSPORT) with a no_application_protocol TLS alert (QUIC error code 0x178; see tls-errors) if an application protocol is not negotiated.  While ALPN only specifies that servers use this alert, QUIC clients MUST use error 0x178 to terminate a connection when ALPN negotiation fails. </ins> An application protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol compatible


generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to improve its RTT estimate, the new packet that it sends MUST be ack- <del> eliciting.  If Handshake keys are available to the client, it MUST send a Handshake packet, and otherwise it MUST send an Initial packet in a UDP datagram of at least 1200 bytes. </del>  Initial packets and Handshake packets could be never acknowledged, but they are removed from bytes in flight when the Initial and its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has <del> not received a HANDSHAKE_DONE frame. </del>  5.2.3.

This is what the pseudocode says, but I couldn't find anywhere in the text that clarified that if there were no ack-eliciting packets to arm PTO from, use Now().

generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to improve its RTT estimate, the new packet that it sends MUST be ack-  <ins> eliciting. </ins> Initial packets and Handshake packets could be never acknowledged, but they are removed from bytes in flight when the Initial and its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has  <ins> not received a HANDSHAKE_DONE frame.  If Handshake keys are available to the client, it MUST send a Handshake packet, and otherwise it MUST send an Initial packet in a UDP datagram of at least 1200 bytes. A client could have received and acknowledged a Handshake packet, causing it to discard state for the Initial packet number space, but not sent any ack-eliciting Handshake packets.  In this case, the PTO is set from the current time. </ins> 5.2.3.


ports RFC8085 and, when possible, connection IDs to an active QUIC session. <del> Further validation can also be provided: An IPv4 endpoint could set the Don't Fragment (DF) bit on a small proportion of packets, so that most invalid ICMP messages arrive when there are no DF packets outstanding, and can therefore be identified as spurious. An endpoint could store additional information from the IP or UDP headers to use for validation (for example, the IP ID or UDP checksum). </del> The endpoint SHOULD ignore all ICMP messages that fail validation. <del> An endpoint MUST NOT increase PMTU based on ICMP messages.  Any reduction in the QUIC maximum packet size MAY be provisional until </del>  QUIC's loss detection algorithm determines that the quoted packet has actually been lost. 14.3.1. <del> Endpoints that rely on the destination connection ID for routing QUIC packets are likely to require that the connection ID be included in PMTU probe packets to route any resulting ICMP messages (icmp-pmtud) back to the correct endpoint.  However, only long header packets (long-header) contain source connection IDs, and long header packets are not decrypted or acknowledged by the peer once the handshake is complete.  One way to construct a PMTU probe is to coalesce (see </del>  packet-coalesce) a Handshake packet (packet-handshake) with a short header packet in a single UDP datagram.  If the UDP datagram reaches the endpoint, the Handshake packet will be ignored, but the short <del> header packet will be acknowledged.  If the UDP datagram elicits an ICMP message, that message will likely contain the source connection ID within the quoted portion of the UDP datagram. </del>  15.

A few minor tweaks based on review of the latest DPLPMTUD draft. If I never have to type DPLPMTUD ever again, I'll be happy.
These comments relate only to the PMTU text in -24, relative to the current DPLPMTUD text as that now progresses to WGLC. /This can also be used to construct PMTU probes (see Section 14.3.1). / Is this necessary? Or can padding frames be used to make (PL)PMTU probes?. My confusion here may be linked to not understanding the intent of 14.3.1 /Further validation can also be provided: o An IPv4 endpoint could set the Don't Fragment (DF) bit on a small proportion of packets, so that most invalid ICMP messages arrive when there are no DF packets outstanding, and can therefore be identified as spurious. o An endpoint could store additional information from the IP or UDP headers to use for validation (for example, the IP ID or UDP checksum)./ I suggest the above text is spurious, since the checks advocated in DPLPMTUD and the QUIC fields provide a way to detect this. While it is possible too toggle the DF field, I do no think the IETF should endorse this, because it can have other side effects and this is not the place to propose that method. /An endpoint MUST NOT increase PMTU based on ICMP messages. / DPLPMTUD already specifies. This has always been the case, so please either delete or cite DPLPMTUD or the two core IP specs. Section 14.3.1 I didn’t understand section 14.3.1. Maybe that isn’t good since I do understand PMTUD and PLPMTUD. Perhaps this section requires some reworking to explain the goal.
Tentatively marking this as editoral; if something design-y pops up when processing please ping.
Let me see if I can break this down a little. cc NAME for his opinion of these. : The first and last comment relates to the section on using coalescing to help the routing of ICMP messages. I had no difficulty reading this section, though I might suggest one tweak: The "further validation" text: DF is currently mandated, but this text pre-dates that requirement. We should drop that clause. And the other clause is a little lame. I'd be happy dropping all of the quoted text. Citing DPLPMTUDTD is sensible and we should do that.
Thanks, LG

ports RFC8085 and, when possible, connection IDs to an active QUIC session.  The endpoint SHOULD ignore all ICMP messages that fail validation.  <ins> An endpoint MUST NOT increase PMTU based on ICMP messages; see Section 3, clause 6 of DPLPMTUD.  Any reduction in the QUIC maximum packet size in response to ICMP messages MAY be provisional until </ins> QUIC's loss detection algorithm determines that the quoted packet has actually been lost. 14.3.1.  <ins> Endpoints that rely on the destination connection ID for routing incoming QUIC packets are likely to require that the connection ID be included in PMTU probe packets to route any resulting ICMP messages (icmp-pmtud) back to the correct endpoint.  However, only long header packets (long-header) contain source connection IDs, and long header packets are not decrypted or acknowledged by the peer once the handshake is complete. One way to construct a probe for the path MTU is to coalesce (see </ins> packet-coalesce) a Handshake packet (packet-handshake) with a short header packet in a single UDP datagram.  If the UDP datagram reaches the endpoint, the Handshake packet will be ignored, but the short  <ins> header packet will be acknowledged.  If the UDP datagram causes an ICMP message to be sent, the first part of the datagram will be quoted in that message.  If the the source connection ID is within the quoted portion of the UDP datagram, that could be used for routing. </ins> 15.


4.5.1.2. <del> The "Base" is used to resolve references in the dynamic table as </del>  described in relative-indexing. To save space, the Base is encoded relative to the Required Insert <del> Count using a one-bit sign ('S') and the "Delta Base" value.  A sign </del>  bit of 0 indicates that the Base is greater than or equal to the value of the Required Insert Count; the decoder adds the value of Delta Base to the Required Insert Count to determine the value of the

The new principle is to only use backticks when referring to variables in pseudocode. There were two such instances that remain.
In I came up with a principle that might apply to use of . Field names, frame names, transport parameter names, and error codes don't use any form of decoration. But if reference is being made to a variable in pseudocode, we would use backticks to distinguish it from. Qpack is a little tricky here because there is a mix of things. Note: backticks are quoted in for HTML and in double quotes () in text output. Sparing use is probably best.
WFM. Thanks. I was never clear on whether "Base" or "Required Insert Count" referred to pseudocode variables or not.

4.5.1.2.  <ins> The Base is used to resolve references in the dynamic table as </ins> described in relative-indexing. To save space, the Base is encoded relative to the Required Insert  <ins> Count using a one-bit sign ('S') and the Delta Base value.  A sign </ins> bit of 0 indicates that the Base is greater than or equal to the value of the Required Insert Count; the decoder adds the value of Delta Base to the Required Insert Count to determine the value of the


recovery.  Packets containing only ACK frames SHOULD therefore not be paced, to avoid delaying their delivery to the peer.  Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations MUST either use pacing or limit such <del> bursts to the initial congestion window, which is recommended to be the minimum of "10 * max_datagram_size" and "max(2 * max_datagram_size, 14720))", where max_datagram_size is the current maximum size of a datagram for the connection, not including UDP or IP overhead. As an example of a well-known and publicly available implementation of a flow pacer, implementers are referred to the Fair Queue packet scheduler (fq qdisc) in Linux (3.11 onwards). </del>  6.10.

This isn't code, because I don't think we need to do any more than point at the leaky bucket algorithm, but it does provide more concrete advice than we've had. It removes the reference to fqdisc as that isn't especially helpful, as the original issue observes.
Leaky bucket sidesteps the issue of shaping multiple streams, but I agree you do not need that level of detail here. I don't understand the motivation for the N factor from a casual read. I guess it requires a deeper insight into the congestion controller. If it is a burst thing, it couldn't be for every interval ?
N exists to deal with the potential for the CWND to increase. Let's say that you have a CWND of 10 packets and an RTT of 1 second. If you pace evenly over that period, you will send one packet every 100ms. If the CWND doubles, you want to send every 50ms instead, but that only starts from the point you apply the increase. This allows you to send a little faster prior to the increase and not need to wait as long for the increase to take effect. N also exists because timers are terrible and you don't want to lag, which would force the pacer to be an extra constraint on send rate. Maybe that's too fancy and it should just be N is a number a little larger than 1.
So you are consuming the CNWD a bit too fast in anticipation of it growing before you run out and if it shrinks instead, I suppose you enter a period of silence because you over-consumed. But couldn't that also be detrimental? Perhaps more so for unreliable transmission with a timing aspect.
The definition of the pacer should not ever limit the congestion window. If the window collapses, then the cap on rate can apply the higher value. I should make that clearer.
Oh, I went back to my code and found why I wanted N. With the same example as before, you are sending 10 packets per second and the congestion window doubles. If you apply the formula in one way, you can get some odd spikes in the rate. Say that you measure over 10s (i.e., 100p/10s), and the rate doubles. If you attempt to reach the new target in the next second (200p/10s), your send rate goes up by a factor of 10 briefly. There is less incentive to do that sort of compensation if you have been sending at a higher rate previously.
I'm using exponential smoothing in other contexts: x + (1 - x). URL
I've taken a little of each suggestion here. I think that the difference between suggesting N=1.25 and N=2 is largely philosophical, so I don't want to get drawn on that. The only constraints that matter is that it be small and >= 1.
I reflowed, then reworded to s/timer/alarm and explain a little more.
The current text in recovery says: Could we change this recommendation to something like "a Fair Queue packet scheduler using an algorithm such as "Weighted Round Robin" or "Deficit Round Robin"? The specific Linux reference is not very helpful in online searches.
The interesting part about the the Linux qdisc is its support for rate-based packet pacing, not it's fair queueing algorithm. Would it help to make that more clear or provide a reference such as: URL
Not really, this is the first (and almost only) thing you find and it tells you very little about queueing algorithms.
I agree. I found this reference pretty odd and it could become dated in time. I don't think we need to refer implementers to any particular implementation.
I think NAME was going to write a more crisp definition of a pacer and remove this reference?
I was . If the formula I described is acceptable, then I'm happy to contribute text, but I hadn't planned to.
NAME I think it is good to have a clear definition of what pacing means when QUIC has that as a requirement for one operational mode. I'm not sure it is really helpful by itself in guiding the user towards queueing disciplines such as deficit round robin. I'd prefer at least a mention of such an algorithm as one way to achieve the desired behaviour rather then referring to qdisc. Such a name can searched and Wikipedia will answer.
We had tried to provide a clearer definition of pacing here: URL This includes specification for how long should an implementation wait between two consecutive bursts: To effectively avoid creating these large packet bursts, a sender SHOULD also enforce a minimum time between sending the above mentioned bursts. To achieve high throughput while at the same time minimizing the bursts it is considered safe to wait for (RTT maximumburstsize / (cwnd 2)) seconds between each burst (multiplying the congestion window by two allows the the rate to increase during slow-start). If the text in looks close to what we want, I can recreate the PR and we can go from there.
Thanks for working through this! I'll wait for NAME to take a look.I have one editorial change on top of this, but I'd like to do it separately.

recovery.  Packets containing only ACK frames SHOULD therefore not be paced, to avoid delaying their delivery to the peer. <ins> Endpoints can implement pacing as they choose.  A perfectly paced sender spreads packets exactly evenly over time.  For a window-based congestion controller, such as the one in this document, that rate can be computed by averaging the congestion window over the round- trip time.  Expressed as a rate in bytes: Or, expressed as an inter-packet interval: Using a value for "N" that is small, but at least 1 (for example, 1.25) ensures that variations in round-trip time don't result in under-utilization of the congestion window.  Values of 'N' larger than 1 ultimately result in sending packets as acknowledgments are received rather than when timers fire, provided the congestion window is fully utilized and acknowledgments arrive at regular intervals. Practical considerations, such as packetization, scheduling delays, and computational efficiency, can cause a sender to deviate from this rate over time periods that are much shorter than a round-trip time. </ins> Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations MUST either use pacing or limit such  <ins> bursts to the initial congestion window; see initial-cwnd. One possible implementation strategy for pacing uses a leaky bucket algorithm, where the capacity of the "bucket" is limited to the maximum burst size and the rate the "bucket" fills is determined by the above function. </ins> 6.10.


send multiple GOAWAY frames indicating different identifiers, but MUST NOT increase the identifier value they send, since clients might already have retried unprocessed requests on another connection.  An endpoint that is attempting to gracefully shut down a connection can send a GOAWAY frame with a value set to the maximum possible

Draft 27 says: Maybe I'm missing it but I can't see where we say how the GoAway receiver handles an seeing the GoAway ID getting increased. We typically state what the receiver should do in such a case. For instance, with MAXPUSHID we say " receipt of a MAXPUSHID that contains a smaller value than previously received MUST be treated as a connection error of type H3IDERROR.". I suggest that we need text that states receiving a larger GOAWAY ID than previously received MUST be treated as a connection error of type H3IDERROR. N.B. since draft-27 we landed changes to make the GOAWAY symmetrical. I believe this issue is still applicable to the most recent editors draft
H3IDERROR SGTM

send multiple GOAWAY frames indicating different identifiers, but MUST NOT increase the identifier value they send, since clients might already have retried unprocessed requests on another connection. <ins> Receiving a GOAWAY containing a larger identifier than previously received MUST be treated as a connection error of type H3_ID_ERROR. </ins> An endpoint that is attempting to gracefully shut down a connection can send a GOAWAY frame with a value set to the maximum possible


4.1. <del> Long headers take the form described in fig-long.  Bits that have version-specific semantics are marked with an X. </del>  A QUIC packet with a long header has the high bit of the first byte set to 1.  All other bits in that byte are version specific. 4.2. <del> Short headers take the form described in fig-short.  Bits that have version-specific semantics are marked with an X. </del>  A QUIC packet with a short header has the high bit of the first byte set to 0.

Happened to notice that the updated figure (didn't check the old one) was inconsistent with the text about the allowed length of the CID fields. Should be editorial, since the text wins any conflicts.
Bah, yes, 255 bytes = 2,040 bits.
:D That's exactly the process that my mind followed.
Both section 4.2 and 4.3 say Unfortunately, the doesn't have Xs. I'm not sure how to reword this to support both formats.
how about :)
I think this was fixed by URL, I dont know if that was just a coincidence or it was untagged
That was intentional. I just neglected to tag this.

4.1.  <ins> Long headers take the form described in fig-long. </ins> A QUIC packet with a long header has the high bit of the first byte set to 1.  All other bits in that byte are version specific. 4.2.  <ins> Short headers take the form described in fig-short. </ins> A QUIC packet with a short header has the high bit of the first byte set to 0.


the byte limit to account for the smaller 8 byte overhead of UDP compared to the 20 byte overhead for TCP.  The minimum congestion window is the smallest value the congestion window can decrease to as a response to loss, ECN-CE, or persistent congestion.  The RECOMMENDED value is 2 * max_datagram_size.

Clarifies the impact of the anti-amplification factor on congestion control. Part of
When the client has a large first flight (post-quantum, or 0-RTT), it can send 10 packets at once. But that does not mean that the server can immediately send as much as 3x it receives in response, because that is above the INITCWND - the initial assumption about the network capacity. Things to be considered here are: Server cannot rely on CC until the client's address is validated, because an attacker might spoof Initial ACKs. At the very least, server needs to send one Initial in response to every client Initial packet not containing ACKs, otherwise the handshake might fail when there is heavy loss. Based on this observation, to me it seems that the easiest way of going forward would be to state that the amplification limit changes from 3x to 1x once the amount of data being sent by the server reaches INITCWND. Disclaimer: this issue popped up while trying to find out a response to NAME claim that quicly sends 3x that is above INITCWND, when the client sends a large amount of 0-RTT data. He's correct, and NAME and I think that this is a spec-issue.
Just to clarify, only an on-path attacker can do this, right?
I agree it should be limited to INITCWND. Here's how I read(and intended to write) the text: 1) Congestion control always applies, because I don't know of anywhere we say otherwise. 2) In addition, prior to address validation, the amplification limit of 3x also applies on the server side. Init CWND section: URL
NAME An off-path attacker can do that. An attacker can easily guess the packet number that the server uses, and spoof an ACK without actually receiving a packet from the server. Note also that there is no guarantee that there would be enough entropy in server-generated CID, even though a server can generate CIDs in such a manner. That's why we postpone the validation of the path until the server receives a Handshake packet. NAME I think I agree with you that the limit can be defined as .
Thanks NAME PTAL at PR and see if that clarifies this issue or if something else is necessary.
At the editor's meeting, we concluded that the pseudocode did not represent the current text about re-arming the PTO alarm. I merged the first PR because I think it's a nice clarification and I'll send out a PR for the pseudocode.
Thank you for the quick PR. I've stated my slight preference, but the text looks already :+1: to me.

the byte limit to account for the smaller 8 byte overhead of UDP compared to the 20 byte overhead for TCP. <ins> Prior to validating the client's address, the server can be further limited by the anti-amplification limit as specified in Section 8.1 of QUIC-TRANSPORT.  Though the anti-amplification limit can prevent the congestion window from being fully utilized and therefore slow down the increase in congestion window, it does not directly affect the congestion window. </ins> The minimum congestion window is the smallest value the congestion window can decrease to as a response to loss, ECN-CE, or persistent congestion.  The RECOMMENDED value is 2 * max_datagram_size.


preferred_address transport parameter. An endpoint SHOULD ensure that its peer has a sufficient number of <del> available and unused connection IDs.  Endpoints store received connection IDs for future use and advertise the number of connection IDs they are willing to store with the active_connection_id_limit transport parameter.  An endpoint MUST NOT provide more connection IDs than the peer's limit.  An endpoint that receives more connection IDs than its advertised active_connection_id_limit MUST close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. </del>  An endpoint SHOULD supply a new connection ID when the peer retires a connection ID.  If an endpoint provided fewer connection IDs than the

The rules for the active connection ID limit were such that the brief bump that NEWCONNECTIONID + Retire Prior To could add was not permitted. Strictly speaking, if you requested retirement, the connection IDs that you requested be retired are still active until you receive a RETIRECONNECTIONID frame. That means that you were technically violating this MUST in the spec. Adding an exception for this case was the best way I could see to fix this. The other was to change the definition of "active connection ID". I tried that and, while it was a smaller change, it made the definition less crisp. This approach seems best. (This is not new normative requirements as this is already .)
LGTMThanks for making (yet) another attempt to clarify this. Looks good.

preferred_address transport parameter. An endpoint SHOULD ensure that its peer has a sufficient number of  <ins> available and unused connection IDs.  Endpoints advertise the number of active connection IDs they are willing to maintain using the active_connection_id_limit transport parameter.  An endpoint MUST NOT provide more connection IDs than the peer's limit.  An endpoint MAY send connection IDs that temporarily exceed a peer's limit if the NEW_CONNECTION_ID frame also requires the retirement of any excess, by including a sufficiently large value in the Retire Prior To field. A NEW_CONNECTION_ID frame might cause an endpoint to add some active connection IDs and retire others based on the value of the Retire Prior To field.  After processing a NEW_CONNECTION_ID frame and adding and retiring active connection IDs, if the number of active connection IDs exceeds the value advertised in its active_connection_id_limit transport parameter, an endpoint MUST close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. </ins> An endpoint SHOULD supply a new connection ID when the peer retires a connection ID.  If an endpoint provided fewer connection IDs than the


the amplitude of amplification attacks caused by server responses toward an unverified client address; see address-validation.  Datagrams containing Initial packets MAY exceed 1200 bytes if the <del> client believes that the Path Maximum Transmission Unit (PMTU) supports the size that it chooses. </del>  UDP datagrams MUST NOT be fragmented at the IP layer.  In IPv4 IPv4, the DF bit MUST be set to prevent fragmentation on the path. A server MUST discard an Initial packet that is carried in a UDP <del> datagram that is smaller than 1200 bytes.  A server MAY also immediately close the connection by sending a CONNECTION_CLOSE frame with an error code of PROTOCOL_VIOLATION; see immediate-close-hs. </del>  The server MUST also limit the number of bytes it sends before validating the address of the client; see address-validation. 14.1. <del> The PMTU is the maximum size of the entire IP packet including the IP header, UDP header, and UDP payload.  The UDP payload includes the QUIC packet header, protected payload, and any authentication fields. The PMTU can depend upon the current path characteristics. Therefore, the current largest UDP payload an implementation will send is referred to as the QUIC maximum packet size. </del>  QUIC depends on a PMTU of at least 1280 bytes.  This is the IPv6 minimum size RFC8200 and is also supported by most modern IPv4

This isn't a complete dissertation on how clients might choose to use larger packet sizes. In particular, it doesn't capture the whole discussion about starting at a higher size being a way to raise the 1280 to something higher, with the requisite cost being that you might have failures. Deciding to put that in would be much harder. What this does is just say that the transport parameter acts as a further limit on packet size and that you won't know that prior to knowing. That means that you are guessing about the network path AND your peer when you decide to send >1200 (to be fair, the former really only applies for IPv4, and it's not much of a guess).
The transport parameter (formerly known as ) instructs the receiving endpoint that it should try to avoid sending packets bigger than that number. However, some packets are sent before the transport parameters are received. Should the draft contain some guidance about what value to use during the handshake? Our implementation currently uses a default value of 1350 until the transport parameters have been received, and I wonder if that may lead to interop issues if some Web servers implement QUIC but drop packets larger than 1300 for example. One could consider using the minimum possible value (1200) during the handshake but I fear that this would negatively impact performance.
This is a good issue. If we are to provide guidance, my preference goes to stating that an endpoint should not send datagrams that are larger than its first flight, unless that endpoint implements PMTUD. The rationale is that we should avoid packets starting to get dropped mid-connection. First flights getting dropped is fine, because clients can do happy-eyeballing. Even if they only use QUIC, not starting a transaction is much better than a transaction inflight getting lost.
I'm with NAME here. As a client, if you send at 1350 and those are all dropped, that reduces to "the path doesn't support UDP". The real risk is if you send at 1200 and then increase that later without first probing. I'm happy to take this as editorial, but would prefer to confirm that no one wants a substantive change.
I agree with NAME : I'm happy with clarifying that this will look like an MTU issue.
One suggestion, but LGTM otherwise.Thanks for updating the adjacent text, this LGTM

the amplitude of amplification attacks caused by server responses toward an unverified client address; see address-validation. <ins> Enforcement of the max_udp_payload_size transport parameter (transport-parameter-definitions) might act as an additional limit on packet size.  Exceeding this limit can be avoided once the value is known.  However, prior to learning the value of the transport parameter, endpoints risk datagrams being lost if they send packets larger than 1200 bytes. </ins> Datagrams containing Initial packets MAY exceed 1200 bytes if the  <ins> client believes that the network path and peer both support the size that it chooses. </ins> UDP datagrams MUST NOT be fragmented at the IP layer.  In IPv4 IPv4, the DF bit MUST be set to prevent fragmentation on the path. A server MUST discard an Initial packet that is carried in a UDP  <ins> datagram with a payload that is less than 1200 bytes.  A server MAY also immediately close the connection by sending a CONNECTION_CLOSE frame with an error code of PROTOCOL_VIOLATION; see immediate-close- hs. </ins> The server MUST also limit the number of bytes it sends before validating the address of the client; see address-validation. 14.1.  <ins> The Path Maximum Transmission Unit (PMTU) is the maximum size of the entire IP packet including the IP header, UDP header, and UDP payload.  The UDP payload includes the QUIC packet header, protected payload, and any authentication fields.  The PMTU can depend on path characteristics, and can therefore change over time.  The largest UDP payload an endpoint sends at any given time is referred to as the endpoint's maximum packet size. </ins> QUIC depends on a PMTU of at least 1280 bytes.  This is the IPv6 minimum size RFC8200 and is also supported by most modern IPv4


A Probe Timeout (PTO) triggers sending one or two probe datagrams when ack-eliciting packets are not acknowledged within the expected <del> period of time or the handshake has not been completed.  A PTO enables a connection to recover from loss of tail packets or acknowledgements. </del>  As with loss detection, the probe timeout is per packet number space. The PTO algorithm used in QUIC implements the reliability functions Sending different payloads each time reduces the chances of spurious retransmission. <del> 5.2.5. Delivery or loss of packets in flight is established when an ACK frame is received that newly acknowledges one or more packets. A PTO timer expiration event does not indicate packet loss and MUST NOT cause prior unacknowledged packets to be marked as lost.  When an acknowledgement is received that newly acknowledges packets, loss detection proceeds as dictated by packet and time threshold mechanisms; see ack-loss-detection. </del> 5.3. A Retry packet causes a client to send another Initial packet,

The fact that PTO doesn't declare losses when it expires is quite important. It at the very end of the section, so I moved it to the first part. Also removed a section heading(pto-loss) which was never referenced.
A reasonable simplification.

A Probe Timeout (PTO) triggers sending one or two probe datagrams when ack-eliciting packets are not acknowledged within the expected  <ins> period of time or the server may not have validated the client's address.  A PTO enables a connection to recover from loss of tail packets or acknowledgements. A PTO timer expiration event does not indicate packet loss and MUST NOT cause prior unacknowledged packets to be marked as lost.  When an acknowledgement is received that newly acknowledges packets, loss detection proceeds as dictated by packet and time threshold mechanisms; see ack-loss-detection. </ins> As with loss detection, the probe timeout is per packet number space. The PTO algorithm used in QUIC implements the reliability functions Sending different payloads each time reduces the chances of spurious retransmission.  5.3. A Retry packet causes a client to send another Initial packet,


This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the <del> congestion controller.  For example, a pacer might distribute the congestion window over the smoothed RTT when used with a window-based controller, or a pacer might use the rate estimate of a rate-based controller. </del>  An implementation should take care to architect its congestion controller to work well with a pacer.  For instance, a pacer might Practical considerations, such as packetization, scheduling delays, and computational efficiency, can cause a sender to deviate from this rate over time periods that are much shorter than a round-trip time. <del> Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations MUST either use pacing or limit such bursts to the initial congestion window; see initial-cwnd. </del> One possible implementation strategy for pacing uses a leaky bucket algorithm, where the capacity of the "bucket" is limited to the

I prefer to have MUSTs towards the top of sections when possible so they're not missed. This is a follow-on from
I'm not a fan of this change -- I think there's value in having that text below where we talk about how bursts can occur. That said, I don't feel strongly about it, so I'm not going to fight for it.

This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the  <ins> congestion controller.  Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations MUST either use pacing or another method to limit such bursts to the initial congestion window; see initial-cwnd. </ins> An implementation should take care to architect its congestion controller to work well with a pacer.  For instance, a pacer might Practical considerations, such as packetization, scheduling delays, and computational efficiency, can cause a sender to deviate from this rate over time periods that are much shorter than a round-trip time.  One possible implementation strategy for pacing uses a leaky bucket algorithm, where the capacity of the "bucket" is limited to the


timer expiring immediately. A sender recomputes and may need to reset its PTO timer every time an <del> ack-eliciting packet is sent.  When ack-eliciting packets are in- flight in multiple packet number spaces, the timer MUST be set for the packet number space with the earliest timeout, except for ApplicationData, which MUST be ignored until the handshake completes; see Section 4.1.1 of QUIC-TLS.  Not arming the PTO for ApplicationData prevents a client from retransmitting a 0-RTT packet on a PTO expiration before confirming that the server is able to decrypt 0-RTT packets, and prevents a server from sending a 1-RTT packet on a PTO expiration before it has the keys to process an acknowledgement. </del>  When a PTO timer expires, the PTO backoff MUST be increased, resulting in the PTO period being set to twice its current value. <del> The PTO period is set based on the latest RTT information after receiving an acknowledgement.  The PTO backoff is reset upon receiving an acknowledgement unless it's a client unsure if the the server has validated the client's address.  Not resetting the backoff during peer address validation ensures the client's anti-deadlock timer is not set too aggressively when the server is slow in responding with handshake data. </del>  This exponential reduction in the sender's rate is important because consecutive PTOs might be caused by loss of packets or

Clarifies the cases when the PTO needs to be recomputed and possibly reset. This was not in a single place before.
NAME : merging this so it makes -28. Filing a separate issue for the leftover editorial fix.
(deleted comment, because the initially mentioned problem was incorrect. the requirement to arm the PTO timer after the handshake finishes is in Section 5.2.2)
There's some architectural inconsistency around the treatment of 1RTT PTOs at the end of the handshake. On the one hand, we are not supposed to set the PTO timer for 1RTT before the handshake is complete. This is to prevent us from retransmitting stuff the peer may not be able to process. On the other hand, we are supposed to arm the PTO timer when the handshake is confirmed. This is the point where we're guaranteed that the peer has receive keys. So there is a weird gap between handshake complete and handshake confirmed where new 1RTT packets will arm the PTO, but outstanding 0RTT or 1RTT packets from before handshake complete will not have an active PTO timer. This isn't the end of the world -- the connection will recover -- but it doesn't make any sense, and could result in either excessive PTO delays, or 1RTT probes that the peer can't decode.
NAME can we get a status update?
The PTO is always armed when there are bytes in flight(except when the loss timer is armed), it's just a matter of what PN spaces it can be armed for at a given point in time. Once the connection is handshake complete, it's immediately allowed to arm it for inflight 1-RTT ack-eliciting packets. Re-reading the text, I couldn't find anything explicitly calling out that the alarm may need to be recomputed and reset in the handshake confirmed case, though it is stated for discarding keys. As clarified in when the client is handshake complete but not confirmed, it is unsure whether the server has Handshake keys or ApplicationData keys, but it's very unlikely to have both. As such, sending something ack-eliciting from both PN spaces is safest.

timer expiring immediately. A sender recomputes and may need to reset its PTO timer every time an  <ins> ack-eliciting packet is sent or acknowledged, when the handshake is confirmed, or when Initial or Handshake keys are discarded.  This ensures the PTO is always set based on the latest RTT information and for the last sent packet in the correct packet number space. When ack-eliciting packets in multiple packet number spaces are in flight, the timer MUST be set for the packet number space with the earliest timeout, with one exception.  The ApplicationData packet number space (Section 4.1.1 of QUIC-TLS) MUST be ignored until the handshake completes.  Not arming the PTO for ApplicationData prevents a client from retransmitting a 0-RTT packet on a PTO expiration before confirming that the server is able to decrypt 0-RTT packets, and prevents a server from sending a 1-RTT packet on a PTO expiration before it has the keys to process an acknowledgement. </ins> When a PTO timer expires, the PTO backoff MUST be increased, resulting in the PTO period being set to twice its current value.  <ins> The PTO backoff factor is reset when an acknowledgement is received, except in the following case.  A server might take longer to respond to packets during the handshake than otherwise.  To protect such a server from repeated client probes, the PTO backoff is not reset at a client that is not yet certain that the server has finished validating the client's address.  That is, a client does not reset the PTO backoff factor on receiving acknowledgements until it receives a HANDSHAKE_DONE frame or an acknowledgement for one of its Handshake or 1-RTT packets. </ins> This exponential reduction in the sender's rate is important because consecutive PTOs might be caused by loss of packets or


To reduce the chances of misinterpreting congestive loss as packets dropped by a faulty network element, an endpoint could set the ECT(0) <del> codepoint in the first ten outgoing packets on a path, or for a </del>  period of three RTTs, whichever occurs first. Implementations MAY experiment with and use other strategies for use

The current text doesn't really suggest that the probes stop.
Currently there is text saying: "To reduce the chances of misinterpreting congestive loss as packets dropped by a faulty network element, an endpoint could set the ECT(0) codepoint in the first ten outgoing packets on a path, or for a period of three RTTs, whichever occurs first." At the interim there was a discussion that today it is likely safe to just make all packets with ECT as the risk of black-holing is not measurable anymore. PR proposes a text change to provide more background. In the comments there is also a link to a measurement which could be added if desired.
This issue has been extensively litigated and I don't think that there is new information that would suggest a change here. Implementations are free to assume that ECN works, but we did previously have consensus to recommend a more conservative approach.
I don't think we had consensus to recommend this and the proposed text makes clear that this is not a recommendation.
Folks who care about a change here need to prioritize progressing this issue. We're close to being able to last-call the specs, and would like this resolved in time.
So I think there might be a point to indicate the current state of affairs when it comes to black holing better. However, it doesn't really change the need for validation as the main issue to check for is actually that the receiver is capable of receiving the ECN bits which has a much higher risk of not being true. I would even argue that the reason for stop sending ECT after a sufficient number of packets that some should make it is to avoid getting an unfair advantage until you know that the code points are read and that you will get an CE mark if it occurs. So maybe the text should be changed to focus on this aspect? Even if the proposed PR is rejected there need to be a change to this sentence: "To reduce the chances of misinterpreting congestive loss as packets dropped by a faulty network element, an endpoint could set the ECT(0) codepoint in the first ten outgoing packets on a path, or for a period of three RTTs, whichever occurs first." There is a missing "only" or something else to indicate that you are sending ECN for a while then going back to not-ECT until validation has occurred.
NAME the validation mechanism itself is there to stop marking packets as ETC when you don't get feedback for any reason. So that's already covered in the text. For this part of the text the question is if you should set all packet as ETC until validation fails or if there are additional risk which justifies a more complicated scheme where only some of the packets in the validation phase are marked as ECT.
MT's proposed editorial change in appears to clarify the intent of the current draft text. Perhaps additional context and discursive text (as proposed in Mirja's ) is more suited to the Applicability draft?
does not address this issue. This was just a side comment that came up. I think the minimum would be to replace "To reduce the chances of misinterpreting congestive loss as packets dropped by a faulty network element..." by "If there is as a concern for a certain network that all ECT-marked packets might be dropped by a faulty network element and would therefore cause a performance drop, then..." However, I would still prefer to give a bit more context than this (basically one more sentence as proposed). I don't think this fits in the applicability statement as I don't think there is any intention here to provide an interface to specify the ECN probing behaviour and therefore a "user" of QUIC, which is the audience for the applicability statement, would not know what to do about this information. If we want an interface and move this to applicability, I think the whole text should be moved there with some more discussion rather than giving a random recommendation in the base spec.
Thanks for the clarification, that makes sense. As someone not familiar with ECN and just reading the specification, I'm struggling to see what the crux of the issue is. And I would hypothesise that this is a contributing factor to it getting stalled. As an inexperienced implementer I find the current text a bit weak but I'm not sure more context helps me. If I were to distil it down I'd take the current text: dropped by a faulty network element, an endpoint could set the ECT(0) codepoint in the first ten outgoing packets on a path, or for a period of three RTTs, whichever occurs first. and simply editorialize it to
This doesn't address my point. Today there is little to no evidence that these faulty network elements still exists. So my recommendation would be to not add any complexity by implementing these schemes. The current text makes it look like this, however, is a common problem, which is not the case, and therefore indictes that this scheme is recommended to be implemented.
Implementations have a bunch of complexity trade-offs to make. Since the text is non-normative I don't feel obliged to implement this technique. And if I understand it correctly, if I did implement this and ran it on a network path where there were no bad elements, then nothing bad will happen?
NAME I don't understand the pushback. As NAME notes, if no faulty network elements exist, the mechanism should work just fine. I think the crux of the disagreement might be that the text suggests that the default stance is test and then use, where perhaps you are arguing for use and fall back. I honestly don't think it makes much of a difference, and in the absence of overwhelming information we need to be taking the conservative stance on this spec.
I'm agreeing with the editors here. I think the PR is a useful modification, but we'd need to see stronger feedback from the WG to warrant making more changes to the current text.
The difference is that I would like to decrease unnecessary barrier to use ECN. If people are too lazy to implement this and don't understand that it is not needed, they might just disable ECN instead. If not sure what you mean by overwhelming information but we have done extensive measurements over 5 years showing that this problem doesn't exists anymore. What else are you expecting?
So far I didnt really see this issuing being coined in terms of "guidance for conservative ECN use risks low uptake of the ECN feature". If that is the core issue, then an example of overwhelming information is to see support for addressing the issue from a number of QUIC implementations that do not support ECN, and have chosen to do so because of this guidance. Since the other text around ECN seems to require per-packet-space counts, and other parts of QUIC require RTT measurement, I'm finding it difficult to see how this is a burdensome thing to implement. But I can only speak to my implementation and experience.
So last I counted there were only four stacks that even implemented the ACK-ECN frame type to report ECN marks. So even very basic support for ECN is (still) missing in most stacks, esp. the ones that are likely going to see the largest deployments. I don't think any text tweaks we could make here are going to move the needle on ECN support in QUIC. What will make a difference if a major stack implements it and reports a benefit. That will likely make other stacks follow.
Implementing this mechanism is trivial for someone implementing the rest of QUIC. It's not a lazy implementer that is causing the problem of lack of implementation, it's an unmotivated one. As NAME notes, in the absence of ACK-ECN, this doesn't even matter. I am not hearing any of the implementers complaining about the complexity of the mechanism here, so I would argue that the mechanism is not an issue.
Looks good to me.

To reduce the chances of misinterpreting congestive loss as packets dropped by a faulty network element, an endpoint could set the ECT(0)  <ins> codepoint for only the first ten outgoing packets on a path, or for a </ins> period of three RTTs, whichever occurs first. Implementations MAY experiment with and use other strategies for use


could choose not to use the preferred address. A server in a deployment that does not implement a solution to <del> maintain connection continuity during connection migration SHOULD disallow migration using the disable_active_migration transport parameter. </del>  Server deployments that use this simple form of load balancing MUST avoid the creation of a stateless reset oracle; see reset-oracle. connection migration before the handshake is confirmed, as defined in section 4.1.2 of QUIC-TLS. <del> An endpoint also MUST NOT send packets from a different local address, actively initiating migration, if the peer sent the disable_active_migration transport parameter during the handshake. An endpoint which has sent this transport parameter, but detects that a peer has nonetheless migrated to a different network MUST either drop the incoming packets on that path without generating a stateless reset or proceed with path validation and allow the peer to migrate. Generating a stateless reset or closing the connection would allow third parties in the network to cause connections to close by spoofing or otherwise manipulating observed traffic. </del>  Not all changes of peer address are intentional, or active, migrations.  The peer could experience NAT rebinding: a change of The disable active migration transport parameter is included if the endpoint does not support active connection migration <del> (migration).  Peers of an endpoint that sets this transport parameter MUST NOT send any packets, including probing packets (probing), from a local address or port other than that used to perform the handshake.  This parameter is a zero-length value. </del>  The server's preferred address is used to effect a change in server address at the end of the handshake, as described in

Following on NAME insight that disableactivemigration is a property of an address, not a peer, we simply need to say which address the TP applies to. This PR by scoping the definition of disableactivemigration to the handshake address only. This means that existing clients remain fully compliant, albeit slightly more conservative than necessary. While I find NAME argument convincing that a server capable of using SPA is highly unlikely to be unable to handle migration, it would be trivial for a future extension to define an additional transport parameter carrying two bits that disable active migration for those addresses as well.
Offshoot from review. Servers behind a load balancer, and the load balancer isn't CID-aware. Each server gives out an IP:port per client in the parameter. If the client moves to the SPA, then future migration is fine -- it will continue to talk to the same server using the preferred address. If the client doesn't move, those future migrations will break. There's currently no way to set to "only if you don't use my preferred address". Similarly, if the primary connection is to an anycast address, if the client fails to move to the preferred address, subsequent migration will likely break. Should we blanket recommend that clients not actively migrate if they ignore / are unsuccessful in using the SPA? Or should there be a way for the server to indicate that?
Good question. This brings up a related question for me about disableactivemigration. Given a server can decide not to give out any additional connection IDs, doesn't that give it a mechanism for disabling active migration? Ironically, given recent decisions on connection CIDs and SPA, if an SPA is supplied, then the client always has a second connection ID it can use on any path, not just the SPA. So my suggestion of not giving out extra CIDs fails for the original path, but not the SPA.
What we’ve said for this so far is that SPA is limited to offering a minimum of a single additional CID that needs to work in both places, the original server and the preferred server. As Ian says, the original server should not issue the client any more CIDs (if you have some that are deployment specific) until after they’ve successfully migrated to the new endpoint, at which point the preferred server can handle that issuance. And as he notes, you still have to deal with the fact that there are two CIDs that the client could use, from any incoming address. It seems as though the full way to solve this is to define potentially disjoint sets of TPs that different servers could set to match their particular requirements (around more than just migration). In that world, if you had an initial server that couldn’t handle migration and a preferred server that did handle migration, you could set different TPs to indicate that. It goes well beyond that though, any other TP might have a different values for the original server and then have those be different with the preferred server. But that does feel an awful lot like something that goes with full server migration. In the limited form that we have now, if you want to be able to push someone off to a different server, there are some limitations that you need to deal with, like being able to set up enough of a shared configuration for the two that the same TPs work with both. In practice, I think this means we end up with the following situation: The client either moves to the requested address or it doesn’t. If it does, then you’re in good shape, in theory you gave it TPs that matched what the server wanted and everybody is happy. If it doesn’t, then you’re unhappy, since in theory you had a reason you asked it to move in the first place. You might choose to close it down more aggressively than you would on the preferred server to save resources, or give it smaller limits for flow control, etc. When you’re in the “client didn’t move” state: If the client then tries to use the original 2nd CID that you gave, from the same IP address/port, then a non-CID aware load balancer should be fine. If the client then tries to use the original 2nd CID that you gave, from a different IP address/port, then a non-CID aware load balancer will not be happy. Of course, the problematic case is even narrower, since if the client is probing the new path they’ll see it as though there isn’t any connectivity, just like they will if there’s anything else on the new path that prevents their connection from working — this is always a risk when migrating, so they should be unsurprised and stick with their current path. If they’re forced to move for whatever reason, then it will be just as though they didn’t have connectivity over the new path (which they don’t) and whether or not you disable migration you’ll see the same outcome — the connection is closed. Interestingly, I’m not actually seeing a difference from the client’s perspective in terms of outcome whether you let them try with that CID and fail or if you told them to not try.
In very much shorter terms: isn’t this a situation that all clients already need to be able to handle for other reasons?
NAME I do not like the first option, because IMO banning migration on the original address is going to be a premature optimization based on what we have now, has negative effect on what we would have in the future. My assumption is that the majority of the server deployments would be CID-aware, especially in the long term, especially in terms of traffic volume. Note that the volume of traffic is more important than the number of server deployments, as migration is about (improving) end-user experience. IMO there are two ways forward: (a) State that disableactivemigration TP only applies to the original address. Per-definition, server-preferred address is an address that is capable of receiving packets from any address. There is no need to prohibit clients doing migration when connected to SPA. (b) Do nothing. Clients are RECOMMENDED to implement SPA, and this issue is about a small fraction of deployments that rely on non-CID-aware load balancers. We can ignore the problem.
IIRC, we had that conversation when deciding whether to add in the first place. The short version is that taking this approach prevents the client from rotating CIDs on the primary path; if we want the client to be able to rotate CIDs but not migrate, they need to be distinct signals. NAME in a way you're correct that clients will have to deal with the fallout regardless of what the server says. However, the actual use of is likely to trigger a little more than just "don't do that" -- a client might PING more actively to prevent NAT rebindings, for example. I think it's this hinting aspect that's actually more valuable, since as you note, clients need to be prepared for attempted migrations to fail even with servers that tacitly support them. I don't think this is necessarily true, though I agree that we're talking about smaller and smaller fractions of server space.
NAME While I agree that it might not necessarily be true at the moment, I might argue that it is going to be true. The basis of the resolution for and (both on consensus call) is that servers using preferred address would not be using the server's address for distinguishing between connections. We also know that a server cannot use client's address (unless there is out-of-band knowledge), because clients are not required to use the same 2-tuple when migrating to a different address (and also because there are NATs). The outcome is that the server has to rely on CID when using SPA. And therefore I might insist that servers using SPA would always be capable of handling further migration.
NAME So your suggestion is that disableactivemigration applies only to the original address and the SPA must always support active migration, because it has to use CID routing? I don't think that'd be a problem for our use cases, but I'm not sure about others.
NAME Exactly. I think that that would be the correct thing to do, if we are to make changes. I agree that existing clients might be applying after migrating to SPA, and that they do not want change. But they can remain as they are, because the change in spec does not lead to interoperability issues. To paraphrase, I think making the aforementioned change is strictly better than doing nothing, because it would be an improvement to some, while not hurting anybody.
A truth table might help here. ... --- no preferred address preferred address Right now, our definition of disableactivemigration is a simple "don't migrate". But the argument here says that the server that provides a preferred address is probably capable of handling migrations on the basis that it has to support one. We might insist on the source address not change, but the fact is that some NATs (those with address-dependent mappings) will change the source address. Changing to a definition where disableactivemigration is instead "don't move to a new address unless you use a preferred address" is a little more complicated, but doable. Is it worth the extra complexity this late in the process? Is that complexity justified when it would be trivially possible to define another extension that said "I support migration on my preferred address"?
I'd like to minimize changes, but I believe the status quo is that disableactivemigration applies to both the original address and SPA. As such, all suggestions above seem like a change. There is a mechanism to disable migration on the SPA, which is don't give out any more CIDs once they migrate. I don't expect any hosts to support migration on the original path and not the SPA, so I don't think that's a use case worth supporting(or encouraging).
My proposal is that we defer the ability to signal "I support migration, but only on my preferred address" to an extension.
NAME And leave the current behavior of disableactivemigration applies to both the original address and SPA? I'm a bit concerned that makes SPA somewhat useless. Is this issue a candidate for parking and we can come back to and see if we can live with the status quo?
Which, again, not only disables migration, it disables the ability to rotate CIDs. That's not something we want servers to be doing. NAME argument is convincing that it's improbable for something which supports SPA to not be able to uniquely identify connections (whether by CID or because the SPA endpoint is unique). Therefore, I'd support scoping disableactivemigration to the handshake address, and defer the ability to disable it on the SPA to an extension. Loosening this definition doesn't make any current clients break, so I suspect it's bearable despite the schedule.
If we're going to make a change, I support NAME and NAME direction of scoping disableactivemigration to the handshake address. And I agree that I don't expect this breaks many existing clients.
I personally find the proposed change confusing. I'm not sure I find the use-case of "my original address doesn't support migration but my preferred address does" compelling. Since this can be easily solved by an extension with no downside over it being in the core docs, I don't see a reason to change the spec at this point in the process.
As this has sat here a while, I'll reiterate my proposal again: we defer the ability to signal "I support migration, but only on my preferred address" to an extension. Which means that disableactivemigration applies to both the original address and SPA, so answer the question (sorry Ian, I missed that). I realize that you could change the meaning of disableactivemigration to apply to the address the server uses in the handshake, but there are interesting corner cases and I don't think we need to concern ourselves with those. For instance, if you connect over V4 and get disableactivemigration and serverpreferredaddress. If that SPA includes a V6 address you can't use, along with the same V4 address, what then? Does the server really support migration? I know that the current situation is unappealing, and I think that disableactivemigration is not good for the protocol. But it exists for very good reasons. Trying to limit the scope is admirable, but as we haven't seen a concrete proposal, I think we need to move on.
NAME If this example is concerning, I'd argue that is an issue that already exists, orthogonal to what we are discussing here. I am still not convinced that limiting the scope of disableactivemigration to just the original address is going to cause any new issue. That said, if nobody needs this change, I'm more than happy to move on with status-quo.
I've thought about this, and I don't believe we need this change, but I also think the change NAME suggested makes the current behavior better overall. A server that supports migration on the SPA(ie: unicast) and not on the handshake address(ie: anycast) will end up not disabling migration and hoping that most clients migrate to the SPA or don't support migration of any sort. That seems like a plausible hope, though I'm a bit leery of hope based protocol design.
Thinking about this more, a statement like: "Migration is never guaranteed to succeed. For servers that advertise a preferred address, it's expected migration is more likely to be successful when using the preferred address than the handshake address."
tl;dr: I'm arguing for "do nothing". This conversation has made it clear to me that an address, and not a server, is the thing that supports client migration. Which makes sense -- migration support at the server is entirely a question of routability, and it makes sense that it would be tied to the address in question and not the endpoint. So if we were to do anything, I think I would propose: scoping disableactivemigration to the handshake address, and adding two disableactivemigration flags to the preferredaddress TP, for the two addresses in the TP. That said, this is a clean enough extension. A new extension can define a new TP that provides this granular interpretation of the three server addresses. A server that wishes to support this can send both the SPA and this newSPA TPs, with the SPA TP (with the current scoping of disableactivemigration applying to all server addresses) providing cover for legacy clients. Also I expect that clients are likely to switch to using the SPA if provided. A server that has different routability properties for the two addresses can wait for some deployment and make decisions on whether to risk allowing migration based on data of what it sees as client behavior with SPA. I would argue for doing nothing now. Experience will teach us the right way forward. If necessary, this extension will be easy enough to do, and importantly, the current interpretation of disableactive_migration gives us the right kind of cover in the meantime.
I would rather not make the change in . It assumes that migration is more likely to succeed when attempted to the preferred address as opposed to the handshake address. While this assumption might hold in some server deployments, it is not a fact. I'd rather we didn't bake this assumption into the core spec. I'd prefer to see this feature as an extension.
NAME Would you mind elaborating why a server, accepting connections on a preferred address, might not be able to disambiguate connections using CIDs? As stated previously, when a client migrates to the preferred address, the client's address port tuple can change due to the existence of NATs. A server using preferred address need to take that into consideration. That means that a server has to disambiguate connections based on the CID, which in turn means that the server would be possible to support migration.
Hmmm thanks for that note NAME Looking at things under that light, I agree that the assumption I described above is already part of the protocol. Please disregard my comment above URL .
Seems fine.Thank you for the changes. I like NAME editorial improvements, but even without them, I think we are ready.

could choose not to use the preferred address. A server in a deployment that does not implement a solution to  <ins> maintain connection continuity when the client address changes SHOULD indicate migration is not supported using the disable_active_migration transport parameter.  The disable_active_migration transport parameter does not prohibit connection migration after a client has acted on a preferred_address transport parameter. </ins> Server deployments that use this simple form of load balancing MUST avoid the creation of a stateless reset oracle; see reset-oracle. connection migration before the handshake is confirmed, as defined in section 4.1.2 of QUIC-TLS.  <ins> If the peer sent the disable_active_migration transport parameter, an endpoint also MUST NOT send packets (including probing packets; see probing) from a different local address to the address the peer used during the handshake.  An endpoint that has sent this transport parameter, but detects that a peer has nonetheless migrated to a different remote address MUST either drop the incoming packets on that path without generating a stateless reset or proceed with path validation and allow the peer to migrate.  Generating a stateless reset or closing the connection would allow third parties in the network to cause connections to close by spoofing or otherwise manipulating observed traffic. </ins> Not all changes of peer address are intentional, or active, migrations.  The peer could experience NAT rebinding: a change of The disable active migration transport parameter is included if the endpoint does not support active connection migration  <ins> (migration) on the address being used during the handshake.  When a peer sets this transport parameter, an endpoint MUST NOT use a new local address when sending to the address that the peer used during the handshake.  This transport parameter does not prohibit connection migration after a client has acted on a preferred_address transport parameter.  This parameter is a zero- length value. </ins> The server's preferred address is used to effect a change in server address at the end of the handshake, as described in