Subject: How fiddly should the QUIC header be?

From: Brian Trammell <[email-masked]>
Date: Wed, 11 Jan 2017 16:31:24 +0100

Greetings, all,

This message concerns several running issues in GitHub, so I figured I'd start the conversation the old fashioned way on the list. Happy to file an issue and to make a PR happen if the outcome of this discussion isn't a unanimous "please go away", though. Apologies for the length. Most of it is ASCII art, which I hope is as fun to read as it was to draw ;)

The -latest version of the QUIC transport draft defines seventeen different possible headers for QUIC packets, ignoring the diversification nonce, which remains under discussion but should in my understanding adds only one additional possible header, and in any case doesn't significantly impact the discussion here. The seventeen headers appear in the appendix to this message.

I note several potential issues with this arrangement, all of which impact 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:

(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. Admittedly, in a world where QUIC is limited to udp/443, this matters much less than it may when other applications are adapted to run on top of it. But given that the only invariants in the header are in the definition of the version space and how to determine whether a version number is present,

I would suggest reducing the number of possible header layouts to reduce complexity, based on the following observations and 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. (I think Martin Duke already made this point?)

(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 (e.g., those created by a reflection attack against an existing UDP service).

(4) Thirty-two bits of version are more than enough. 24 suffice. (I note this will require a rethought of the version number space. This should probably also play nicely with DNS/NTP reflection, other widely deployed UDP protocols, and RFC 7983. I leave this to future work.)

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."

I note that this proposal is completely incompatible with the previous header. But we already paid for a flag-day by switching endianness. It makes sense to make full use of the flag day we've bought, in order to make the header (especially the parts of it we can't change) less fiddly.

(I also personally think the truncated header is of little use; eleven bytes don't cost much, we could get back a flags bit and some complexity if we just said here's your sixteen octet header, enjoy. But this proposal gives us some of the performance benefits of the fiddly header without all the fiddling.)

Cheers,

Brian

Appendix:

Here are the seventeen headers I found.

First, a set of conventions for the diargams. Let's name the flags:

0 = Reserved (always zero)
M = Multipath (always zero)
P = Packet number side
C = Connection ID present
K = Key phase
R = public Reset
V = Version present

+-+-+-+-+-+-+-+-+
|0|M| P |C|K|R|V|
+-+-+-+-+-+-+-+-+

Header 0: flags and packet number, 2 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 0|0|0|0|0|  packet num   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 1: Version present, 6 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 0|0|0|0|1|             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |  packet num   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 2: Connection ID present, 10 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 0|1|0|0|0|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |  packet num   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 3: Connection ID and version present, or version negotiation, 14 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 0|1|0|0|1|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |  packet num   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 4: flags and packet number, 3 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 1|0|0|0|1|         packet number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 5: Version present, 7 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 1|0|0|0|1|             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |         packet number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 6: Connection ID present, 11 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 1|1|0|0|0|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |         packet number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 7: Connection ID and version present, or version negotiation, 15 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0 1|1|0|0|1|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . .  (cont'd) |         packet number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header 8: flags and packet number, 5 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 0|0|0|0|1|         packet number                     . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . .  (cont'd) |
+-+-+-+-+-+-+-+-+

Header 9: Version present, 9 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 0|0|0|0|1|             Version number                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |         packet number                     . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |
+-+-+-+-+-+-+-+-+

Header 10: Connection ID present, 13 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 0|1|0|0|0|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |         packet number                     . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |
+-+-+-+-+-+-+-+-+

Header 11: Connection ID and version present, or version negotiation, 17 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 0|1|0|0|1|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |         packet number                     . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |
+-+-+-+-+-+-+-+-+

Header 12: flags and packet number, 9 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 1|0|0|0|1|                                               |
+-+-+-+-+-+-+-+-+                packet number                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |
+-+-+-+-+-+-+-+-+

Header 13: Version present, 13 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 1|0|0|0|1|             Version number                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |                                               |
+-+-+-+-+-+-+-+-+                packet number                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |
+-+-+-+-+-+-+-+-+

Header 14: Connection ID present, 17 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 1|1|0|0|0|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |                                               |
+-+-+-+-+-+-+-+-+                packet number                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |
+-+-+-+-+-+-+-+-+

Header 15: Connection ID and version present, or version negotiation, 21 octets

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1 1|1|0|0|1|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |             Version number                . . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . . (cont'd)  |                                               |
+-+-+-+-+-+-+-+-+                packet number                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |
+-+-+-+-+-+-+-+-+

Header 16: Public Reset, no version or packet number, 9 octets.

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|? ?|1|0|1|0|                                               |
+-+-+-+-+-+-+-+-+                Connection ID                  |
|                                                               |
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               |
+-+-+-+-+-+-+-+-+

---

From: "Eggert, Lars" <[email-masked]>
Date: Thu, 12 Jan 2017 09:18:08 +0000

Hi,

(chair hat off)

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

I do share this sentiment (see https://github.com/quicwg/base-drafts/issues/40). And it's not only the common header; the various frame type headers have a similar plethora of field length options.

Lars

---

From: Brian Trammell <[email-masked]>
Date: Thu, 12 Jan 2017 10:49:11 +0100

> On 12 Jan 2017, at 10:18, Eggert, Lars <[email-masked]> wrote:
> 
> Hi,
> 
> (chair hat off)
> 

Agreed.

I chose to focus on the packet header first for a couple of reasons. First, I've spent more time looking at it recently in the context of draft-kuehlewind-quic-appman (see Mirja's message). Second, reducing the attack surface presented by packet header parsing code seems more critical than reducing the attack surface presented by frame parsing code; the later requires an attacker to be able to establish a cryptographic session with a target, while the former merely requires an attacker to be able to send a UDP packet to the target.

(The use of magic numbers or other techniques in order to ensure that an initial packet can't be forged through reflection/amplification is probably an issue we should discuss separately from overhauling the header, though the solution might come in the same PR. I'll go ahead and file in github.)

Cheers,

Brian

---

From: "Dirk Kutscher" <[email-masked]>
Date: Thu, 12 Jan 2017 22:20:31 +0100

Hi,

On 12 Jan 2017, at 10:18, Eggert, Lars wrote:

>> I would suggest reducing the number of possible header layouts to 
>> reduce complexity, based on the following observations and 
>> assumptions:
>
> I do share this sentiment (see 
> https://github.com/quicwg/base-drafts/issues/40). And it's not only 
> the common header; the various frame type headers have a similar 
> plethora of field length options.

Yes, I had the same thoughts.

Dirk

---

From: Brian Trammell <[email-masked]>
Date: Fri, 13 Jan 2017 10:30:50 +0100

Filed in GitHub; see https://github.com/quicwg/base-drafts/issues/147 and https://github.com/quicwg/base-drafts/issues/148

Cheers,

Brian

> On 12 Jan 2017, at 22:20, Dirk Kutscher <[email-masked]> wrote:
> 
> Hi,
> 
>