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

QUIC packet header complexity #148

Closed
britram opened this issue Jan 13, 2017 · 11 comments
Closed

QUIC packet header complexity #148

britram opened this issue Jan 13, 2017 · 11 comments
Assignees
Labels
-transport design has-consensus

Comments

@britram
Copy link
Contributor

@britram britram commented Jan 13, 2017

The current design of the header with its multiple variable length and variable presence fields would seem to require inordinate sender and receiver code complexity. Complexity, especially in parsers (i.e., on the receiver side), is a source both of unclarity in specifications and errors in implementation. I note the following specific issues:

(1) There are thirteen possible header lengths, and therefore data offsets : 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 17, 21. Each of the header fields has a variety of possible offsets. This also increases the complexity of hardware offload implementations of QUIC, but is not complex enough to prevent their implementation and deployment.

(2) There are combinations of flags that seem to be nonsensical and/or contradictory (e.g., the Packet Number Length codepoint adds 1, 2, 4, or 8 to the length if the Public Reset flag is clear, but 0 to the length if the Public Reset flag is set, see ; Public Reset and Version are mutually exclusive.)

(3) There appears to be no consideration given to reducing confusability with other deployed UDP protocols in the first N bytes of the header -- see #147

I would suggest reducing the number of possible header layouts to reduce complexity, based on the following assumptions:

(1) The set of variable header lengths will, at most, save a number of bytes per packet that will often be lost in the underflow of suboptimal path MTU discovery.

(2) Varlen packet numbering makes sense if the packet number always starts from zero, but random initial packet numbers have other advantages; I presume we'll adopt them at some point. Packet numbers only need to be large enough to keep wrap-arounds to be induced by reordering, so 32 bits (as in the TCP sequence number space) is more than sufficient.

(3) There is value in treating a version number as a magic number to help reduce the chance of misrecognition of non-QUIC packets as initial QUIC packets at an endpoint; for this 32 bits of version are more than enough, and 24 probably suffice. (This will require a redesign of the version number space, which we might want to do in light of #147 anyway).

As an initial proposal, I would propose that either both Connection ID and Version be present, or neither, and that packet number be always present and constant length. This reduces the set of necessary flags to three, leaving us with five flags for reserved/future use (four, if we go ahead and allocate multipath) and the following two header layouts:

Full Header: 16 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|R|K|M| zero  |      version/magic (24 bits)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-                      Connection ID                          -+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Packet Number                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Truncated Header: 5 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|R|K|M| zero  |                       Packet Number       . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |
+-+-+-+-+-+-+-+-+

The header invariant then becomes "If bit 0x80 in octet 0 is 1, then the seven remaining bits of that octet are reserved for use as flags, and octets 1-3 are a 24-bit version number. If bit 0x80 in octet 0 is 0, then no version number is present; use previous version information for this 5-tuple." This can be further simplified by dropping the truncated header entirely, though for very short packets the overhead might be worth saving.

@britram
Copy link
Contributor Author

@britram britram commented Jan 13, 2017

See also #40

@janaiyengar
Copy link
Contributor

@janaiyengar janaiyengar commented Jan 13, 2017

I appreciate the spirit of reducing the header's complexity, and so happy to see where we can do it. At the same time, It's not clear to me what problem we're solving by reducing the number of header possibilities. Of course, where it makes sense, I agree that reducing complexity helps, but note that the packet format in the common case is quite fixed (no version negotiation, connection ID either present or absent, PR absent, etc.) Besides, flag bits always have nonsensical combinations which should cause the receiver to do something bad to the connection.

I'm not sure that fixing the connection ID and version to be present at all times makes sense. That's just throwing away bits. Sure, we lose some to PMTUD, but surely that's not an argument for losing more willy-nilly.

@janaiyengar janaiyengar added the needs-discussion label Jan 14, 2017
@martinthomson martinthomson added the design label Jan 15, 2017
@ianswett
Copy link
Contributor

@ianswett ianswett commented Jan 16, 2017

We should definitely reduce complexity where-ever possible, and there are likely some opportunities in the header. But it'd very difficult to make it simpler and maintain two design goals I believe are very important:

  1. Minimal framing overhead for applications that are sensitive to it(VoIP is one)
  2. Future proofing QUIC for another 20+ years

1 byte packet numbers aren't hugely useful, but 2 byte ones are very common. And as much as it seems insane to have packet numbers larger than 4 bytes, I fear we'd want it later.

I think the issue of suboptimal MTU is not that large. Right now we're always within 10% of optimal, and commonly closer to 5%. 5% more packet overhead is 3 bytes for IPv6. But the complexity of making packet number and other variable length encodings work is tiny compared to doing PathMTU properly.

It's also worth keeping in mind this will become ossified, so at some point we won't be able to easily change this via version negotiation.

@britram
Copy link
Contributor Author

@britram britram commented Jan 16, 2017

a couple of replies...

I'm not sure that fixing the connection ID and version to be present at all times makes sense.

Fixing the version to be always present would IMO tend to reduce the tendency for ossification, by allowing stateless handling of QUIC version on the path. Well-chosen version numbers that always appear at the same point in the packet have the added advantage of fixing #147.

Fixing the connection ID on all packets seems to make less sense. However, if the connection ID is to be used as a hard-to-guess value to confirm state rebinding, it does have to appear on those packets used to re-establish state along the path.

Minimal framing overhead for applications that are sensitive to it(VoIP is one)

This is a good argument for having a header that can get very small (and a semi-facetious side question: how small does the QUIC header have to get before the header compressors in common use decide not to bother with it?). A byte of flags and however many bytes you need to represent packet numbers would seem to be the minimum here ( unless you want to do tricks like defining 4 flag bits and an unsigned28 packet number). And as noted, you can only get down to 3 or 5 byte headers in this case. Is the 2 bytes per packet of overhead worth an extra flags bit and varlen complexity?

Future proofing QUIC for another 20+ years

1 byte packet numbers aren't hugely useful, but 2 byte ones are very common. And as much as it
seems insane to have packet numbers larger than 4 bytes, I fear we'd want it later.

I'm not sure I buy flows with more than four billion packets in the reorder window, even twenty years from now. But nobody will ever need more than 640k, either, so let's assume 64 bit packet numbers are useful.

I'd argue the right way to do this is to have a version mechanism that'll actually get used, and to make those packet numbers a feature of a future version. One way to do this, if one is prepared to have version numbers on all (or at least most) packets, would be to have the version number reflect a packet version/variant as well as a protocol version: (most of the) higher order bits designate protocol versions, with (a small number of) lower order bits to be used by that protocol version for the different variants of packets they use.

@martinthomson
Copy link
Member

@martinthomson martinthomson commented Jan 17, 2017

I would have thought that signaling version in every packet, aside from being grossly inefficient (yes, people complain about every octet still), would encourage more ossification. Let QUIC version N through and block everything else is a very predictable response. That leads to everyone using QUIC version N.

As for sizes on things, I can see cases for packet numbers of 2 and 4 octets, and think that defining a new version is the rational response to a need for more. It's probably the 640k thing all over, but even though I can't see far enough into the future to see that being a mistake, I can see as far as a new version (even if that turns out to be the problem that triggers the creation of a new version).

@britram
Copy link
Contributor Author

@britram britram commented Jan 17, 2017

I would have thought that signaling version in every packet, aside from being grossly inefficient (yes, people complain about every octet still), would encourage more ossification.

packet-size-matters vs. packet-size-doesn't-matter (rephrased as bytes-congest vs. packets-congest) might be TSV's official vi vs. emacs fight. so if we want to have it let's open another issue for it. :)

The problem here is, without a crystal ball, both "versioning leads to ossification" and "versioning prevents ossification" have believable elements. It depends in large part whether the people making the devices that lead to ossification treat the version number as something that will change, or as something that is labeled as "version" but really means "magic".

Adding an option for u16 packet numbers saves 2 octets per packet compared to u32 packet numbers, but adds the complexity of variable field offsets. u16 packet numbers are probably fine for most situations today -- even shaving a bit for wrap-around detection, both the receiver and passive observers will have an accurate view of what's happening as long as all your reordering gaps are less than 32ki packets. So I think I'm more sold on the idea of 2-octet packet numbers than I am on the idea of 2-or-4 octet packet numbers.

@mirjak
Copy link
Contributor

@mirjak mirjak commented Jan 17, 2017

I think you can have mostly the same ossification if you only have the version number on the first packet (meaning middleboxes block packets where the version bit it set and they don't like the version they see). All information that is visible at some point will be ossified. Having the version flag always only set on the first packet, will however make this packet specially and middelboxes will use this signal to identify the first packet of a connection. That's worst from my point of few because that'd be a wrong interpretation of the signal (while the interpretation that a certain version number identifies a certain version is true).

Also +1 to larger packet numbers can go with a new version if we ever need them in future.

@mnot mnot changed the title Reducing the complexity of the QUIC packet header QUIC packet header complexity Jan 20, 2017
@mnot
Copy link
Member

@mnot mnot commented Jan 26, 2017

As discussed in Tokyo, editors to go through their respective headers and come up with proposals to potentially limit variability. Same as #40

@mnot mnot closed this Jan 26, 2017
@mnot
Copy link
Member

@mnot mnot commented Jan 26, 2017

@ekr put your number here

@martinthomson martinthomson reopened this Jan 26, 2017
@ekr
Copy link
Collaborator

@ekr ekr commented Jan 26, 2017

I was going on about two options:

option 1: special bit
option 2: magic token

@mnot mnot removed the needs-discussion label Jan 26, 2017
@martinthomson
Copy link
Member

@martinthomson martinthomson commented Mar 10, 2017

Closed with #361. Please reopen more specific issues for areas in which you disagree with that design.

@mnot mnot added the has-consensus label Apr 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
-transport design has-consensus
Projects
None yet
Development

No branches or pull requests

8 participants