Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify version negotiation #2133

Closed
wants to merge 7 commits into from
Closed

Simplify version negotiation #2133

wants to merge 7 commits into from

Conversation

ekr
Copy link
Collaborator

@ekr ekr commented Dec 12, 2018

Following the discussions in BKK I've been thinking about version
negotiation a bit, and I think we can improve things while also
simplifying them. The TL;DR version of this is that instead of having
the client send any version and the server correct it, the client
offers a list of versions and the server picks one. This makes
things a lot simpler at the cost of some unnecessary flexibility.

DETAILS
The basic insight is that the current VN process is complicated
because it's got an axis of flexibility we don't need, namely allowing
the client to send an Initial packet with no idea whatsoever of what
the server supports. This means that you have to be able to have the
server blindly generate a VN no matter what the client sends, which
has the following negative consequences:

  • It burns up a round trip if the client's initial version is not
    the one the server wants, even if the client and server are
    basically compatible.

  • We want it to be stateless, so we have this clumsy validation
    procedure.

What I propose is a simpler approach, which is pretty much what
all other other IETF protocols that do version negotiation do:

  • The client sends an Initial packet using a version which is
    acceptable to every server for a given application (e.g., Web,
    e-mail, etc.). This packet contains a list of the versions
    the client supports.

  • The server responds with its preferred version out of this list.

  • If the client's Initial packet is totally incomprehensible to
    the server, then you get a hard failure (and we repurpose
    and simplify VN for this).

This has the following advantages:

  • All version negotiation happens in one round trip.

  • Negotiation validation is mostly a matter of checking that the
    server-selected version is acceptable [0].

The main cost is that the client needs to be able to safely send an
Initial that can be read by the server, which means that it needs to
agree with the server on Initial format, though this can span multiple
QUIC versions.

Importantly, this doesn't preclude having totally incompatible
versions of QUIC (e.g., with a different CRYPTO handshake). You can
obviously have two disjoint sets of clients and servers that support
QUIC X and QUIC Y. Additionally, the case where you have a server
which supports A and B and clients which either support A or B works
fine. The client's version appears in the header and the server can
demux on that. So, it's not like we're promising that every version of
QUIC has the same CRYPTO values, just that the client knows enough
about the server to know an Initial format it will accept.

What won't work is where the client has no knowledge of the server's
capabilities at all, specifically where (a) the client supports
incompatible versions A and B and (b) the server might support only A
or only B and the client doesn't know which one. It's not clear how
this could happen in practice; it's like the client wanting to speak
either TLS or SSH and not knowing which one the server speaks [1].

[0] You also want to put the server's selected version in the crypto
handshake, so you need to check that the inner and outer versions
match.

[1] If we ever did want this, we could always design it as an
extension later, presumably cribbing from the current VN design.

@mikkelfj
Copy link
Contributor

Can you summarise how this differ from the previous failed attempt at this?

#1901

@ekr
Copy link
Collaborator Author

ekr commented Dec 12, 2018

Well, I'm not sure I would say the previous version failed, but in any case, the previous version had two separate mechanisms, one for "compatible" versions and one for "incompatible". This is just the "compatible" piece (with some slightly different mechanisms). See above for my argument for why we don't need the "incompatible" piece.

@DavidSchinazi
Copy link
Contributor

I think this may be premature, for the same reasons as the previous proposal was. This will be useful once we start working on QUICv2, but by then odds are we will want to do version negotiation differently, for example to define how some QUICv1 features map to their QUICv2 counterparts. Once we know what that looks like, we'll be able to add you new client transport parameter as an extension. I don't think there's much value in saving one RTT between draft versions (which can be achieved via Alt-Svc) so I still think punting this to when we are closer to QUICv2 is best.

@ekr
Copy link
Collaborator Author

ekr commented Dec 12, 2018

@DavidSchinazi the problem is that we already have a version negotiation mechanism that is much more complicated than this one, and this is a net simplification. So, if your argument was that we should remove VN entirely, that would be different, but if we're going to have some VN, then it might as well be a simpler one.

@DavidSchinazi
Copy link
Contributor

The current VN mechanism has the property of being part of the protocol invariants, whereas the new one only works if the server can parse CRYPTO frames and a TLS 1.3 client hello. Requiring all future versions to be able to parse past versions' transport parameters does not sound simple to me.

@mikkelfj
Copy link
Contributor

I must say that I was not fond of the positive/negative list juggling in the previous approach, and the potential for missing attack vectors or making implementation mistakses, but I also do find current vneg complex.

What is the thinking on downgrade attacks?

Consider babyalarm / widely deployed IoT device offering 3 versions A, B, and C. Later it turns out version B is broken. Compromised MITM device plays valid endpoint and selects version B. Real server cannot ignore version B because MITM got in the way. Not sure the current state is much better on this account?

I good thing about client publishing versions is that a constrained device can offer versions in order of preference rather than conservatively choosing what is likely supported. Likewise an advanced client could prefer unlikely new version, but offer the common version also.

Resolving preferences becomes a bit more involved when the server also has a preference that is not necessarily aligned with the clients. For example servers may prefer AES acceleration over a ChaCha only version (I know TLS is also negotiating crypto - but why, really?).

@MikeBishop
Copy link
Contributor

This feels like doubling down on the previous idea of mutually-intelligible "compatible" versions -- now you're essentially saying that, while QUIC has stated invariants, there are walled gardens of version sets within those invariants which are all QUIC but are not straightforward to use together. That's trading potential implementation simplification for deployment and maintenance complexity, isn't it?

I agree that this is a simplification by reducing an axis of movement. I remain unconvinced that we can guarantee all versions of QUIC used for a given scenario will be "compatible."

@ekr
Copy link
Collaborator Author

ekr commented Dec 12, 2018 via email

@ekr
Copy link
Collaborator Author

ekr commented Dec 12, 2018 via email

@mikkelfj
Copy link
Contributor

Having VN out of invariants makes it possible to improve VN in the future, for example if some unforeseen vulnerability is found.

@MikeBishop
Copy link
Contributor

Yes. My argument is that this is basically the situation we have had with every TCP-using protocol forever. Nobody thinks you can send an HTTP request to a SSH server and have that work, but we do expect SSH clients and servers to be able to negotiate with each other despite some diversity of versions.

Except the equivalent of having QUIC version failure is an inability to do a TCP handshake, which does currently work between unrelated applications. However, I think you're right that the larger point is:

The invariant you need to retain isn't that all versions are compatible but that the client never has to guess whether to send an Initial of type A or B because the server might not understand A but might understand B (or vice versa). I've been trying to think of a non-hypothetical situation in which this might arise that can't be easily dealt with by retaining compat of the Initial. Do you have one?

To some extent, Google's use of QUIC Crypto is an existence proof. There are servers which speak only QUIC Crypto, and there are servers which speak only TLS-based iQUIC. Even if QUIC Crypto is extinct by the time we ship this draft, the fact that the situation has arisen once suggests that it could again.

"Easily dealt with" is pretty dismissive of not only being able to parse old versions' framing, but being able to parlay the handshake contents into a different handshake protocol.

@ekr
Copy link
Collaborator Author

ekr commented Dec 12, 2018 via email

@MikeBishop
Copy link
Contributor

Then your point (2) was bleeding into (1). I don't disagree that we can, but then we have both client-selects and server-selects models, and I don't think our net complexity has reduced. If we're going to start with one and build the other if/when we need it, let's start with the more general-purpose solution.

@chris-wood
Copy link
Contributor

chris-wood commented Dec 13, 2018

I slightly prefer this to the existing VN mechanism for the reasons outlined above. Its simplicity is a particularly appealing property. Moreover, absent a specific case where we will need to deal with complete incompatibility, which I don't foresee happening, the current generality seems to be a case of YAGNI. (That said, I recognize its relation to the invariants may be concerning. And also I am wrong a lot.)

@martinthomson
Copy link
Member

This seems like a proposal that needs to go to the list.

@martinthomson martinthomson added design An issue that affects the design of the protocol; resolution requires consensus. -transport -invariants labels Dec 13, 2018
@ekr
Copy link
Collaborator Author

ekr commented Dec 13, 2018

Acknowledged. I will write a note to the list and we can discuss there.

@kazuho
Copy link
Member

kazuho commented Dec 14, 2018

@chris-wood

Moreover, absent a specific case where we will need to deal with complete incompatibility, which I don't foresee happening, the current generality seems to be a case of YAGNI. (That said, I recognize its relation to the invariants may be concerning. And also I am wrong a lot.)

FWIW, I see two inflexibilities in the Initial packet of QUIC v1 that might make us want to introduce an incompatible version in the future:

  • The packet is authenticated using GCM, and it is impossible to introduce another authentication method.
  • The only extension point is in the ClientHello extension, which means that the future versions of QUIC impossible to signal different information when they retransmit the Initial.

The first point is what prohibits I-D: Authenticated Handshake for QUIC from using an Initial packet format that is compatible with version 1. In case we decide to allow "downgrade" from using authenticated Initials to pure-v1, we need incompatible version negotiation.

@DavidSchinazi
Copy link
Contributor

I've had a few conversations with @ekr and other folks on this topic and here is a proposal I think might gain consensus: we define version negotiation in QUICv1 as a a non-recoverable error:

  • we keep the invariants as they are today
  • in v1 we say that receiving a version negotiation packet is a non-recoverable connection failure
  • we remove the list of supported versions from transport parameters in v1
  • we punt extra-round-trip-less version negotiation to QUICv2; in other words when we are closer to knowing what QUICv2 looks like we will define a QUICv1 extension and normative text for how to negotiate v1 vs v2 while securing against downgrades

@mikkelfj
Copy link
Contributor

in v1 we say that receiving a version negotiation packet is a non-recoverable connection failure

How would you return that error when there is no established connection to close and no common error format.

@kazuho
Copy link
Member

kazuho commented Dec 22, 2018

@DavidSchinazi I think I might oppose to the idea due to the following reasons.

  • in v1 we say that receiving a version negotiation packet is a non-recoverable connection failure

This means that clients that prefer a newer version of QUIC cannot connect to a v1 server, unless it uses an Initial packet that is compatible with that of v1. However, as I have pointed out in
#2133 (comment), the Initial packet design of v1 is rather inflexible. I'd assume that there's fair chance that future version of QUIC requiring to use a format that is incompatible from that of v1.

The other issue is that we'd no longer be able to grease the version field.

@ekr
Copy link
Collaborator Author

ekr commented Dec 22, 2018

@DavidSchinazi I think I might oppose to the idea due to the following reasons.

  • in v1 we say that receiving a version negotiation packet is a non-recoverable connection failure

This means that clients that prefer a newer version of QUIC cannot connect to a v1 server, unless it uses an Initial packet that is compatible with that of v1.

I don't believe that this is correct. In David's proposal, this would work as follows. In v1, we would just specify the VN/fail mechanism. So, if the client sends a v2 Initial, you get VN. However, in v2 we would specify a response similar to that in QUIC currently, namely that the client chooses the new version and then comes back with an Initial. So, if the client sends v2 to a v1-only server, they get VN and fall back to v1, with no change to the v1 server.

Note that this does not have an anti-downgrade mechanism, but we can add one retroactively: v2 clients send supported_versions and v2 servers have to check it, so if you are downgraded from v2 -> v1 you will detect it.

It's also worth noting that even though in principle QUIC now supports this use case, it requires a round trip from the client to the server, so I'm skeptical that clients will do it until the point where they are very unhappy about QUICv1.

@DavidSchinazi
Copy link
Contributor

@kazuho, I agree with @ekr here. The important property is that when a new version of QUIC starts taking shape, that version will define how to VN between it and v1.

@kazuho
Copy link
Member

kazuho commented Dec 24, 2018

@ekr @DavidSchinazi Thank you for the clarifications.

I'm glad to know that we would have VN-packet-based rejection in the new proposal. I'd assume that we would continue to allow (or encourage) clients to grease the servers.

So now it seems to me that the core of the proposal is to omit servers from sending supported_versions and clients checking the value. I can understand that the approach works, because checking supported_versions is meaningless for a client that only supports one version, and because future versions of the client can interpret the omission of the field as the server supporting only version 1.

Generally speaking, I agree that compatible version upgrade is preferable, not only because it reduces one round-trip, but because it is easier to deploy than VN-packet-based negotiation (see quicwg/ops-drafts#52).

If we can anticipate that having only compatible version upgrade would be fine, I think I have no reason to complain against dropping supported_versions from the QUIC v1.

And that's takes me back to wonder about the inflexibility of the v1 Initial packet design.

As I've stated, current design is inflexible in sense that it does not have a way to have extensions outside the ClientHello message. The only way for future versions of QUIC to introduce extensions outside of ClientHello, while retaining compatibility with v1, would be to abuse the Token field. They would define extension slots (like the one we have in TLS handshake messages) using the Token field so that additional data and the token can be transmitted.

Would we be fine with that? If we think that's too ugly, maybe we should add a extensions field to the client-sent Initial packets, so that future versions of QUIC can use that to contain extensions. QUIC v1 client would set the field to zero-length, and v1 servers would ignore the value of the field.

@DavidSchinazi
Copy link
Contributor

I would be supportive of an extension mechanism for client-originated Initial packets.

@martinthomson
Copy link
Member

#2313 was the accepted resolution here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
-invariants -transport design An issue that affects the design of the protocol; resolution requires consensus.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants