Change:
Clients MUST ignore any received Version Negotiation packets that contain the version that they initially attempted.  Once a client has reacted to a Version Negotiation packet, it MUST drop all subsequent <del> Version Negotiation packets. </del> <ins> Version Negotiation packets on that connection. </ins> Servers MUST validate that the client's "Currently Attempted Version" <del> matches the version in the long header that carried the transport parameter.  Similarly, clients MUST validate that the server's "Negotiated Version" matches the long header version.  If an endpoint's validation fails, it MUST close the connection with an error of type VERSION_NEGOTIATION_ERROR. When a server parses the client's "version_negotiation" transport parameter, if the "Received Negotiation Version Count" is not zero, the server MUST validate that it could have sent the Version Negotation packet described by the client in response to an Initial of version "Previously Attempted Version".  In particular, the server MUST ensure that there are no versions that it supports that are absent from the Received Negotiation Versions, and that the ordering matches the server's preference.  If this validation fails, the server MUST close the connection with an error of type VERSION_NEGOTIATION_ERROR.  This mitigates an attacker's ability to </del> <ins> matches the version in the long header that carried the handshake version information.  Similarly, clients MUST validate that the server's "Negotiated Version" matches the long header version.  If an endpoint's validation fails, it MUST close the connection.  If the connection was using QUIC version 1, it MUST be closed with a transport error of type "VERSION_NEGOTIATION_ERROR". When a server parses the client's handshake version information, if the "Received Negotiation Version Count" is not zero, the server MUST validate that it could have sent the Version Negotation packet described by the client in response to a first flight of version "Previously Attempted Version".  In particular, the server MUST ensure that there are no versions that it supports that are absent from the Received Negotiation Versions, and that the ordering matches the server's preference.  If this validation fails, the server MUST close the connection.  If the connection was using QUIC version 1, it MUST be closed with a transport error of type "VERSION_NEGOTIATION_ERROR".  This mitigates an attacker's ability to </ins> forge Version Negotiation packets to force a version downgrade. If a server operator is progressively deploying a new QUIC version throughout its fleet, it MAY perform a two-step process where it first progressively adds support for the new version, but without <del> enforcing its presence in Received Negotiation Versions.  Once all </del> <ins> enforcing its presence in "Received Negotiation Versions".  Once all </ins> servers have been upgraded, the second step is to start enforcing <del> that the new version is present in Received Negotiation Versions. </del> <ins> that the new version is present in "Received Negotiation Versions". </ins> This opens connections to version downgrades during the upgrade window, since those could be due to clients communicating with both upgraded and non-upgraded servers. <ins> If an endpoint receives its peer's Handshake Version Information and fails to parse it (for example, if it is too short), then the endpoint MUST close the connection.  If the connection was using QUIC version 1, it MUST be closed with a transport error of type "TRANSPORT_PARAMETER_ERROR". </ins> 6. <del> The server's Supported Version list allows it to communicate the full list of versions it supports to the client.  In the case where </del> <ins> The server's "Supported Version" list allows it to communicate the full list of versions it supports to the client.  In the case where </ins> clients initially attempt connections with the oldest version they support, this allows them to be notified of more recent versions the server supports.  If the client notices that the server supports a

Discussion:
This PR rewrites most of the draft to make it more aligned with the fact that it's now a stand-alone document that is not directly tied to QUICv1.
NAME defining compatible is currently left to future documents. For example QUICv2 could define its compatibility with QUICv1, or it could decide to skip that and rely on a future document. We could theoretically use draft29 vs QUICv1 as an experiment, but I know we wouldn't implement it because that one is really tricky due to the transport parameter codepoint.
Do we explicitly state this? Should we (can we?) require future documents/versions to respect this process? Even if they're incompatible with other versions, should they reference and require usage of this extension? Would it even still be an extension or directly included? I do think it would be a good test case. It might be practically be useful as well, as v1 and -29 start being supported in parallel in the coming months. Perhaps a separate issue should be opened to track this though.
We explicitly say that versions are incompatible unless a future document says otherwise. We shouldn't require anything. This is just an extension, and we may replace it with a different extension some day. A separate issue sounds best, could you file it please?
Ok. Sounds good. opened.
Not necessarily. We could say that for version B, the long header could have either A or B. This basically requires reinterpreting LongHeader.Version as "format" or "minimum version to interpret" or something, but it's not inherently a problem.
Merged based on offline conversation with NAME
Looking at , how should the server respond if the Client Handshake Version Negotiation Information is malformed? For example, the QUIC version in the long header is different from the Current Attempted Version field in the transport parameter? Should the server continue with the Original Version in the long header (assuming it is supported), or attempt incompatible version negotiation? (related to )
If the Client Handshake Version Negotiation Information is malformed (too short to be parsed or something like that) or if it fails to validate (e.g. Current Attempted Version field differs from the long header's version) then the server MUST close the connection with VERSIONNEGOTIATIONERROR. I've added text to 's "Version Downgrade Prevention" section to make that explicit.

According to the principles we used in QUIC v1, I would've expected a TRANSPORTPARAMETERERROR if parsing fails and a VERSIONNEGOTIATIONERROR if validation fails.
My confusion comes from the line in Compatible Version Negotiation: It seems ambiguous whether this is referring only to the attempt to convert the received flight to a newer version, or whether it also includes the potential failures in parsing. NAME brings up a good point too.
I see. I've clarified that sentence to explicitly rule out parsing and validation failures. I also changed the error code.
Looks good to me! Thanks
Assume that both endpoints support compatible versions and , but the client prefers version over version , while the server prefers version over version . When the server receives a connection attempt using version , it will detect that the client also supports , and convert it to version . The connection therefore ends up with version . Is that what we want? One could argue that the version in use should be controlled by the client, not by the server. This could be easily achieved by only allowing the server to switch to version if the client expressed a preference for over .
Related: NAME comment on URL
Yes, I believe that having the server choose is the right answer. We already have experience with it in TLS where it works reasonably well. It's also hard to enforce the client chooses rule when the client speaks first and offers all its supported versions. The server can just pretend it only supports B and just speaks the A Initial format.
I agree with NAME letting the server choose whether to follow the client's preference or not seems simplest.
Let's assume both client and server support compatible versions and , and the server prefers over . The client now initiates a connection using version , for some reason though the conversion from to fails. As the draft is written now, the only way for the server to proceed here is to perform Incomptabile Version Negotiation, i.e. send a VN packet. While this rule makes the process deterministic (and might allow the client to verify the result of the version negotiation, see the open question in URL), it also costs a round trip. When the differences between and are small, a server might prefer to not bother with upgrading the version, and just establish a connection using . Should we allow that?
I don't understand the scenario you have in mind here.

How would the conversion from X to Y fail? This seems like it shouldnt' happen and should just cause connection failure.
NAME Looking at , it says:
OK, well I haven't reviewed but that sounds wrong to me.
You're right NAME that was an oversight. In , I've replaced: with:
The primary versions available to us to test this new version negotiation process are draft-29 and upcoming v1 versions. It probably makes sense to define the compatibility rules for these so that we can test everything out.
My main concern is that these two versions are very hard to implement compatibility for, because of the TLS codepoint change. Because of that, I don't think Google would implement this. But if we have multiple implementors interested, then we should do it.
I agree it would be tricky. I'd have to double check our logic in MsQuic, but I think it would be easy enough on the server side, but definitely trickier on the client side.
Since RFC 9000 has been published, I think interest in draft-29 is declining. NAME how do you feel about closing this issue?
Sure, good ahead.
Related to URL Should the version negotiation transport parameter allow for (optionally) encoding transport parameters specific to each compatible version? This might improve the range of compatible versions in the future. For instance, if version 2 changed the meaning of one of the existing transport parameters such that it wasn't directly compatible with version 1's TPs, this would allow the versions themselves to still be considered compatible, because the client could just offer the v2 TPs along with the v1.
NAME I'm not sure I understand the problem that this solves. If there is a v2-only TP, the client can send it in v1 and rely on the fact that v1 servers ignore unknown TPs. Could you write up a specific example where this would be useful?
Any kind of breaking change to an existing transport parameter, where the new encoding format in v2 is incompatible with v1. For instance, say was changed to require two numbers, a v4 and v6 specific value. I know this is a contrived example, but if something like it happens in the future, the client cannot send a value for 'maxudppayloadsize` that generically works for both v1 and v2.

Without this feature, v1 and v2 would have to be considered incompatible. Another example, would be a transport parameter that is explicitly disallowed to be sent by the client in v1, but then is allowed (required?) in v2. If it's not required, the client just wouldn't be able to take advantage of the feature tied to the TP, but if it goes from disallowed to required, then the versions become incompatible. For example .
This doesn't sound like the right solution. If we want to change the format of in v2, we can give it a new TP ID. That allows the two versions to be compatible. Similarly, if we want to switch a TP from disallowed to required, we can give it a new ID. Do you have any use-cases that can't be solved by changing the Transport Parameter IDs when changing a TP's meaning?
What about if any transport parameter goes from required in v1 to disallowed in v2?
As I mentioned in URL , in that scenario we'd give this TP a new ID in v2.
Yeah, I guess. You just never have a breaking change by constantly appending new transport parameters and ignoring old/deprecated ones. Then the client just sends the superset of all parameters for all versions. It just means you can never actually change the meaning of a transport parameter for with a particular numeric value. If folks are fine with that restriction for transport parameters, then I'm fine closing this issue. One thought though then, when connecting using version negotiation, the client might use the lowest compatible version number for the packet, but it should supply the highest compatible (superset) version of the transport parameters. Does the spec call this out already?
However, as noted in URL, we have created such a breaking change between draft versions and version 1. The TLS codepoint used for the transport parameters changed between versions. This needs to be addressed in the definition of being able to craft an appropriate Initial in the new version, because there are multiple paths one could choose: Require the client to include both TP extensions, so the server can respond only to the one used by the QUIC version it selects. This pair of versions might be either compatible or incompatible, depending on whether the client includes both TPs in its ClientHello.

This solution isn't desirable long-term, because we don't want QUIC clients squatting on private-use space indefinitely. Require the server to modify the client's TLS message in well-defined ways as part of constructing an up-level Initial. If the client doesn't have a way to make those changes to an in-progress handshake and/or restart a handshake by inputting a ClientHello, it will fail. PSK binders could complicate this further; this would likely mean it's not possible to do some compatible version changes while doing 0-RTT. So while the contents of the TP shouldn't be breaking between versions, we have an existence proof of breaking changes in the means of carrying the transport parameters.
Consider the case of two difference QUIC versions which are roughly identical in terms of the crypto/handshake/packet layer. But imagine that these two versions, say, renumber streams or some other change which makes 0-RTT data look different. I would assume that such versions would not be "compatible" for the purposes of section 7? Or alternatively, they could be considered compatible, but the server MUST not process 0-RTT packets, and the client must "unwind" the 0-RTT state (un-open streams, etc)? (Issue copied from , by NAME on 2019-11-04)
from NAME on 2019-11-04: I agree, we should add text to discuss this, perhaps in a section of recommendations for writers of new versions. When deciding whether two versions are compatible, the cost of rewriting 0-RTT data from one version to the next should be taken into account.
from NAME on 2019-11-19: Interested in seeing where this goes.
It definitely should discuss 0-RTT. I suspect the same language could hold, in that for certain pairs of versions it might be possible to construct up-level 0-RTT packets from down-level 0-RTT packets and pretend that's what you received. However, I suspect that won't be true for all compatible version pairs; tolerating that would mean that you have versions which are Initial-compatible and versions which are I/0-compatible, and you'd have to define behavior between them. It might be simpler to say that if the server wants to change versions, it MUST reject Early Data.

While I recognize that it is an explicit design choice of this extension to move all the downgrade detection to the server, it seems unfortunate to prevent the client from detecting the downgrade and instead relying on the server to do so. (I could imagine that some server implementations are lazy/broken and might not perform this detection)? Alternatively, if the server delivered to the client a set of lists of versions that the server (or the server's peers) might have sent in VN packets, then the client could compare the actual VN packet it received with the elements in the set and detect the case where none of them match. Commonly the server would send one such list, but if it were participating in a staggered rollout, it could send both the "old" and "new" lists? (Issue copied from , by NAME on 2019-11-04)
The contains all versions supported by the server, so the client could easily perform that verification, taking care to: remove the current version from remove any greasing version both from and from the versions in the VN packet ignoring the ordering of versions (dependent on the outcome of URL) Open question: Should the client also check that the server actually performed a version upgrade, if there's a version supported by both endpoints with a higher preference than the currently selected version?
NAME what is "Server Handshake Version Information"? I don't see that string in this draft. Is it in a different doc?
Sorry, I was looking at , as I assumed that’s the direction this draft is moving in.
Oh, I see. Yes, I agree that fixes this issue.
Comment from NAME on 2019-11-04:
Comment from NAME on 2019-11-06 :
If there is a definition for a mapping from vM to vN, can a client assume that the server will understand that mapping? Or does the client have to accept an incompatible version negotiation from servers? It would be nice if compatible mappings could be developed separately from QUIC versions, which suggests that the server can decide whether to apply the mapping. (Issue copied from , by NAME on 2019-11-12)
Copy of the conversation from the original issue: NAME >Can you say more about "mapping"? I don't believe that appears in this document, so I'd like to make sure we are on the same page. NAME >Sorry, I was using my own model.

If you can use an initial flight from A to negotiate B (i.e., there is a mapping from that flight in A to the equivalent in B), then can the client expect that the server will understand that the two are compatible? NAME NAME NAME NAME NAME NAME NAME bunch of unnecessary confusion
I believe clients always have to accept the incompatible-version path. If the client offers A and knows that a mapping from A to B exists, that doesn't mean the server possesses that mapping. From the server's point of view, if it only implements B, it's receiving a packet offering an unknown version. It will send a VN packet, and expect the client to retry with B. The only way around that is if the client also knows that the spec for B requires supporting a mapping from A. I don't think we can assume that's the general case, though it might happen in some cases.
In the common scenario where one has not deployed a new version to 100% of all potential endpoints all the time, the probability of a successful upgrade is p^n, where: p = probability of landing on server with a proposed version being accepted n = number of "nonsticky" round trips If the routing of the packets for an attempted session initiation/connection are routed to the same place, it becomes far more predictable. All of the above is a long way of saying that compatible is not the same as in-line / routed to the same place. Providing an in-line upgrade capability allows a reasonably understandable site deployment to do an upgrade without surprising weirdness. Without this, even with "compatible" upgrades, there will be some "fun" deployment issues in practice. (Issue copied from , by NAME on 2019-11-19)
Generally, looks to be a good rewrite. Thanks! Beyond the one small nit, I have a question about what document has the responsibility to describe/define what versions are compatible and how. This document by definition is an extension to the QUIC spec. The same may continue to be true for QUIC v2, right? If neither spec directly acknowledge this document, it seems they wouldn't specify how compatibility may work. If so, is it the responsibility of this extension to be updated with all known compatibility rules? Additionally, would/could we use -29 and v1 as test candidates for this process? Should we define the compatibility rules in this doc?

Some notes: Section 3: “Version negotiation takes place after the retry cycle is over.” This is only possible for compatible versions; if the server does not support the version the client attempted, it obviously cannot send a Retry packet in that version, because Retry packets are version-specific. Section 3: “Servers must retain the ability to process the Initial packet from older versions….” No, it means that if the server wants to avoid a VN packet, it needs to be able to process the Initial packet from the version the client tried. Section 4: Rather than putting a count-prefixed list of versions, perhaps consider just embedding the VN packet payload? It’s equivalent, but easier to explain. Section 7: Clearly it is possible to possible to add new frame types to Initial packets in a future version, I think what you mean to say is that this is a change that would break compatibility between versions. As a general statement on the document, you assume throughout that all versions have packet types, contain packet types called Initial and Retry, and that Initial packets contain frames. The odds of those things being true across QUIC versions is likely high, but they’re not invariants, so you can’t assume them here. You need to state general principles, and then use concepts from QUICv1 as examples of the principle. For example, rather than discussing version compatibility in terms of Initial packets, you might want to say that versions A and B are “compatible” if the first flight of packets sent by the client in version A carries enough information for the server to generate the corresponding flight in version B, the server speaks version B, and is able to parse at least the first flight of version A. Also not discussed is the case where the server speaks version(s) which are compatible with what the client offered, but doesn’t speak the version the client offered, even enough to generate the response. With the right definition of “compatible,” this just falls into the “incompatible” path and works for the server, but you need to call out the fact that the client shouldn’t consider it an error if the server uses the incompatible path between versions that the client thinks are compatible.

From: QUIC On Behalf Of David Schinazi Sent: Monday, November 4, 2019 3:30 PM To: QUIC Subject: QUIC Version Negotiation Extension Hi everyone, From the recent discussion on multiple PRs, it appears that there might be a need for downgrade-safe QUIC version negotiation, earlier than we expected. EKR and I updated our extension to hopefully suit these needs: URL Comments welcome here or on the GitHub (link in draft). Thanks, David
Hi David, I think that this is broadly the right mechanism. It would be good to validate it more thoroughly. The model and framing could use a little work. I struggled a little with the framing here because the split between incompatible and compatible versions is very important, but also unclear. The definition of "compatible" is left for Section 7, but it seems like it should be right up front. The framing I would choose puts the model up front. The model that you have have two layers that are applied progressively: incompatible version negotiation, where the server sends a Version Negotiation packet in response to a version it does not understand. compatible version negotiation, where the server can generate interpret a version X Initial packet and continue with version Y. Regarding the model, the definition of "compatible" needs work. I observe that you only need a mapping from "Initial"-equivalent packets in X to the same in Y - and maybe the ability to generate a Retry in version X (more below) - in order for this to work. That's where there is a clear functional mapping from a version X packets that initiate the connection to something that fills the same purpose in version Y. But the definition you have for "compatible" implies that the mapping is bijective, which I don't think is necessary. As the draft says, version Y might define (and allow) new frame types in its Initial-equivalent packet(s), but as long as those are either optional or can be synthesized from X, that's OK. It isn't necessary that every Y have a functional representation in X. We might not want to define a way to negotiate an older version from a newer one (or at least not for every case). The ability to generate a Retry stretches this definition a little. Can you explain why you can't generate a version Y Retry?

I have an inkling, but it's not fully formed. It complicates the model more than I'd like to have it this way around.