Change:
14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here. <ins> 2. If A and B are two distinct versions of QUIC, A is said to be "compatible" with B if it is possible to take a first flight of packets from version A and convert it into a first flight of packets from version B.  As an example, if versions A and B are absolutely equal in their wire image and behavior during the handshake but differ after the handshake, then A is compatible with B and B is compatible with A. Version compatibility is not bijective: it is possible for version A to be compatible with version B and for B not to be compatible with A.  This could happen for example if version B is a strict superset of version A. Note that version compatibility does not mean that every single possible instance of a first flight will succeed in conversion to the other version.  A first flight using version A is said to be "compatible" with version B if two conditions are met: first that version A is compatible with version B, and second that the conversion of this first flight to version B is well-defined.  For example, if version B is equal to A in all aspects except it introduced a new frame in its first flight that version A cannot parse or even ignore, then B could still be compatible with A as conversions would succeed for connections where that frame is not used.  In this example, first flights using version B that carry this new frame would not be compatible with version A. When a new version of QUIC is defined, it is assumed to not be compatible with any other version unless otherwise specified. Similarly, no other version is compatible with the new version unless otherwise specified.  Implementations MUST NOT assume compatibility between versions unless explicitly specified. Note that both endpoints might disagree on whether two versions are compatible or not.  For example, two versions could have been defined concurrently and then specified as compatible in a third document much later - in that scenario one endpoint might be aware of the compatibility document while the other may not. </ins> 3. <del> The mechanism defined in this document is straightforward: the client maintains a list of QUIC versions it supports, ordered by preference. Its Initial packet is sent using the version that the server is most likely to support (in the absence of other information, this will often be the oldest version the client supports); that Initial packet then lists all compatible versions (compatible-versions) that the client supports in the Compatible Version fields of its transport parameters (fig-client-tp).  Note that the client's compatible version list always contains its currently attempted version. If the server supports one of the client's compatible versions, it selects a version it supports from the client's compatible version list.  It then responds with that version in all of its future packets (except for Retry, as below). If the server does not support any of the client's compatible versions, it sends a Version Negotiation packet listing all the versions it supports. If the server leverages compatible versions and responds with a different version from the client's currently attempted version, it MUST NOT select a version not offered by the client.  The client MUST validate that the version in the server's packets is one of the compatible versions that it offered and that it matches the negotiated version in the server's transport parameters. If the server sends a Retry, it MUST use the same version that the client provided in its Initial.  Version negotiation takes place after the retry cycle is over. In order for negotiation to complete successfully, the client's Initial packet (and initial CRYPTO frames) MUST be interpretable by the server.  This implies that servers must retain the ability to process the Initial packet from older versions as long as they are reasonably popular.  This is not generally an issue in practice as long as the overall structure of the protocol remains similar. </del> <ins> This document specifies two means of performing version negotiation: one "incompatible" which requires a round trip and is applicable to all versions, and one "compatible" that allows saving the round trip but only applies when the versions are compatible. The client initiates a QUIC connection by sending a first flight of QUIC packets with a long header to the server INV.  We'll refer to the version of those packets as the "original version".  The client's first flight includes handshake version information (see hs-vers- info) which will be used to optionally enable compatible version negotation (see compat-vn), and to prevent version downgrade attacks (see downgrade). Upon receiving this first flight, the server verifies whether it knows how to parse first flights from the original version.  If it does not, then it starts incompatible version negotiation, see incompat-vn.  If the server can parse the first flight, it can either establish the connection using the original version, or it MAY attempt compatible version negotiation, see compat-vn. Note that it is possible for a server to have the ability to parse the first flight of a given version without fully supporting it, in the sense that it implements enough of the version's specification to parse first flight packets but not enough to fully establish a connection using that version. 3.1. QUIC connections are shared state between a client and a server INV. The compatible version negotiation mechanism defined in this document (see compat-vn) operates inside of a QUIC connection; i.e., the packets with the original version are part of the same connection as the packets with the negotiated version.  On the other hand, the incompatible version negotiation mechanism, which leverages QUIC Version Negotiation packets (see incompat-vn) conceptually operates across two QUIC connections: one before the Version Negotiation packet, and a distinct connection after. 3.2. The server starts incompatible version negotiation by sending a VN packet, listing all the versions that it does support. Upon receiving the VN packet, the client will search for a version it supports in the list provided by the server.  If it doesn't find one, it aborts the connection attempt.  Otherwise, it selects a mutually supported version and sends a new first flight with that version - we refer to this version as the "negotiated version". The new first flight will allow the endpoints to establish a connection using the negotiated version.  The handshake of the negotiated version will exchange handshake version information (see hs-vers-info) required to ensure that VN was genuine, i.e. that no attacker injected packets in order to influence the VN process, see downgrade. 3.3. When the server can parse the client's first flight using the original version, it can extract the client's handshake version information (see hs-vers-info).  This contains the list of versions that the client thinks its first flight is compatible with. If the server supports one of the client's compatible versions, and the server also believes that the original version is compatible with this version, then the server converts the client's first flight to that version and replies to the client as if it had received the converted first flight.  The version used by the server in its reply is refered to as the "negotiated version".  The server MUST NOT reply with a version that is not present in the client's compatible versions, unless it is the original version. If the server does not find a compatible version, it will use the original version if it supports it, and if it doesn't then the server will perform incompatible version negotiation instead, see incompat- vn. For the duration of the compatible version negotiation process, clients MUST use the same 5-tuple (source and destination IP addresses and UDP port numbers).  During that time, clients MUST also use the same Destination Connection ID, except if the server explicitly instructs the client to use a different Destination Connection ID (for example, a QUIC version 1 server can accomplish this by sending an INITIAL packet with a Source Connection ID that differed from the client's Destination Connection ID).  This allows load balancers to ensure that packets for a given connection are routed to the same server. </ins> 4. <del> This document registers a new transport parameter, "version_negotiation".  The contents of this transport parameter depend on whether the client or server is sending it, and are shown below: </del> <ins> During the handshake, endpoints will exchange handshake version information, which is a blob of data that is defined below.  In QUIC version 1, the handshake version information is transmitted using a new transport parameter, "version_negotiation".  The contents of handshake version information depend on whether the client or server is sending it, and are shown below (using the notation from the "Notational Conventions" section of QUIC): </ins> The content of each field is described below: <del> The version that the client is using in this Initial.  This field MUST be equal to the value of the Version field in the long header that carries this transport parameter. </del> <ins> The version that the client is attempting to use.  This field MUST be equal to the value of the Version field in the long header that carries this data. </ins> <del> If the client is sending this Initial in response to a Version Negotiation packet, this field contains the version that the client used in the previous Initial packet that triggered the </del> <ins> If the client is sending this first flight in response to a Version Negotiation packet, this field contains the version that the client used in the previous first flight that triggered the </ins> version negotiation packet.  If the client did not receive a Version Negotiation packet, this field SHALL be all-zeroes. A variable-length integer specifying the number of Received Negotiation Version fields following it.  If the client is sending <del> this Initial in response to a Version Negotiation packet, the </del> <ins> this first flight in response to a Version Negotiation packet, the </ins> subsequent versions SHALL include all the versions from that Version Negotiation packet in order, even if they are not supported by the client (even if the versions are reserved).  If

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.