Subject: can h2 extension frames carry data?

From: Matthew Kerwin <[email-masked]>
Date: Mon, 2 Nov 2015 21:47:15 +1000

Hi folks, I have a simple question: can HTTP/2 extension frames be used to
send data?

In draft-kerwin-http2-encoded-data I've defined the GZIPPED_DATA frame,
which is just like DATA but gzipped. It's protected by a setting which
defaults to 0, and you MUST NOT send a GZIPPED_DATA frame unless the peer's
setting is 1.

The problem I'm facing is, what happens if a buggy peer sends the frame at
the wrong time (or, potentially worse, if a buggy peer sends the setting
incorrectly?) Because "implementations MUST discard frames that have
unknown or unsupported types," we can end up silently losing data, with no
way to detect it, let alone throw an error or recover.

In HTTP/2 we've been pretty good at backing up most MUST NOTs with
detectable errors, and I don't feel comfortable introducing something where
the "or else" is "the page you requested/sent evaporates silently."

Is this a deficiency of HTTP/2's extensibility model? Or am I missing a
simple way to make it work?

Cheers
-- 
  Matthew Kerwin
  http://matthew.kerwin.net.au/

---

From: Mike Bishop <[email-masked]>
Date: Mon, 2 Nov 2015 16:12:04 +0000

The answer to your first question is clearly yes – provided the peers agree that a new alternative to the DATA frame exists before it actually goes on the wire, it’s perfectly valid.

Your latter question is a little more nuanced – what happens if two peers have different ideas of what a setting or frame type means?  Fundamentally, that’s why we have a registry of settings and frame types, of course, but there’s not a good way to determine that someone else has a bug there.

Other protocols have dealt with this by having a “mandatory to understand” bit on extensions, but that drags in other interop problems, and it still doesn’t help you in the case of a peer that thinks they understand, but are wrong.  I don’t see a good way to catch such an error, nor even a simple way we could have changed the protocol to make it detectable.  If a peer promises to support a feature but doesn’t, that’s a pretty fundamental brokenness.

Ultimately, I think we just trust in the uniqueness of values on the registry.  If someone seriously screws it up, they’ll find that they don’t have payload matching the Content-Length header they’ve received, and hopefully alarm at that point.  Not true of every request, but if you consistently have this bug, you should hit it quickly enough.

Is there value in a DROPPED_UNKNOWN_FRAME frame?

From: [email-masked] [mailto:[email-masked]] On Behalf Of Matthew Kerwin
Sent: Monday, November 2, 2015 8:47 PM
To: [email-masked]
Subject: can h2 extension frames carry data?

Hi folks, I have a simple question: can HTTP/2 extension frames be used to send data?

In draft-kerwin-http2-encoded-data I've defined the GZIPPED_DATA frame, which is just like DATA but gzipped. It's protected by a setting which defaults to 0, and you MUST NOT send a GZIPPED_DATA frame unless the peer's setting is 1.

The problem I'm facing is, what happens if a buggy peer sends the frame at the wrong time (or, potentially worse, if a buggy peer sends the setting incorrectly?) Because "implementations MUST discard frames that have unknown or unsupported types," we can end up silently losing data, with no way to detect it, let alone throw an error or recover.

In HTTP/2 we've been pretty good at backing up most MUST NOTs with detectable errors, and I don't feel comfortable introducing something where the "or else" is "the page you requested/sent evaporates silently."

Is this a deficiency of HTTP/2's extensibility model? Or am I missing a simple way to make it work?

Cheers

---

From: Cory Benfield <[email-masked]>
Date: Mon, 2 Nov 2015 16:23:58 +0000

> On 2 Nov 2015, at 16:12, Mike Bishop <[email-masked]> wrote:

I don’t think so. However, I *do* think there’d be value in a DROPPED_UNKNOWN_FRAME error code that can be sent on a RST_STREAM frame. This would allow us to say that implementations MAY send a RST_STREAM frame with that error code set if a frame is dropped.

You could even go further and have a flag on frames that requires doing that (a MUST_ERROR_IF_DROPPED flag, essentially). This allows for implementations to say that certain extension frames are critical to understand, and if you don’t understand them you need to let the other implementation know.

Of course, changing the frame header at this late stage is probably too late, but in hindsight this seems like a nice thing to have had.

Cory

---

From: Matthew Kerwin <[email-masked]>
Date: Tue, 3 Nov 2015 06:27:20 +1000

On 03/11/2015 2:24 AM, "Cory Benfield" <[email-masked]> wrote:
> I don’t think so. However, I *do* think there’d be value in a
DROPPED_UNKNOWN_FRAME error code that can be sent on a RST_STREAM frame.
This would allow us to say that implementations MAY send a RST_STREAM frame
with that error code set if a frame is dropped.

Actually, having it as an extension frame type makes sense, almost
ironically, because it can be ignored itself if the original frame sender
doesn't care that the original frame was dropped (as is the case with most
meta extension frames, like in alt-svc.)

I'd half considered the idea of a GZIPPED_DATA ACK signal, but I think a
generic NAK makes more sense. If it were to be specified in a very simple
extension of its own, with clear utility, it could stand a much higher
chance of being widely implemented.

For a RST_STREAM to work, it probably should be baked into h2 itself;
otherwise you roll a die everytime you send extensions to a peer (will they
silently drop it, or loudly fail?)

> You could even go further and have a flag on frames that requires doing
that (a MUST_ERROR_IF_DROPPED flag, essentially). This allows for
implementations to say that certain extension frames are critical to
understand, and if you don’t understand them you need to let the other
implementation know.
>
> Of course, changing the frame header at this late stage is probably too
late, but in hindsight this seems like a nice thing to have had.

Yeah. I seem to recall that this was actually brought up at some point
while extensibility was being discussed -- possibly it was a frame id mask
rather than a flag, but same diff. The fact is it's not in h2, so we can't
rely on it; perhaps h3 will have it, but that depends on things like the
interop issues Mike mentioned.

Finally, Mike, thanks for pointing out that content-length might be useful
in detecting lost frames. I think there's something in that, which I can
write up.

Cheers

---

From: Amos Jeffries <[email-masked]>
Date: Tue, 3 Nov 2015 17:04:26 +1300

On 3/11/2015 9:27 a.m., Matthew Kerwin wrote:
> On 03/11/2015 2:24 AM, "Cory Benfield" wrote:
>>
>>
>>> On 2 Nov 2015, at 16:12, Mike Bishop wrote:

No you don't. When the peer does not also send the same extension in its
SETTINGS, or does not ACK it (depending on what the extension requires).
Then it does not support that extension.

Sending extension frames without explicitly knowing the peer can accept
them is *your* implementation being broken. At the very least it will be
wasting bandwidth and time. At worst it will be screwing up
transactions. Neither is good.

Amos

---

From: Matthew Kerwin <[email-masked]>
Date: Tue, 3 Nov 2015 14:52:47 +1000

> transactions. Neither is good.
​"​Implementations MUST ignore unknown or unsupported values in all
extensible protocol elements.  Implementations MUST discard frames
that have unknown or unsupported types.  This means that any of these
extension points can be safely used by extensions without prior
arrangement or negotiation." RFC 7540, Section 5.5.

So while it could be a buggy implementation's fault, it could also be that
the extension was just written that way. I can send some inconsequential
but interesting ​frame* whenever I like, and according to RFC 7540 that's
fine; but the proposed RST_STREAM means some recipients might throw an
error back at me. So I say again, for an error to work it should be baked
into h2 itself. Here and now, the horse has already bolted.

And yes, it is the buggy/broken implementations I'm concerned about. In
almost every other part of the protocol, if someone in the wild screws up
there's a red flag which we can use to enter an error state. If someone
screws up this particular extension, data just evaporates, and we have to
rely on other heuristics, like content-length mismatches (which only works
if there is a content-length header, and violates layering, etc.), to
detect it.

* e.g. ALTSVC

---

From: Mike Bishop <[email-masked]>
Date: Tue, 3 Nov 2015 05:27:08 +0000

Effectively, there are two types of extensions:

1.      Those that modify base semantics.  These have to be negotiated before use.

2.      Those that don’t modify base semantics.  These can be sent optimistically; if the peer doesn’t speak it, though, you’re wasting bytes.

For type #1, Matthew has raised the question of whether you need a second-line defense against someone who has negotiated in error.  In one of my original extension drafts, I used a larger space to identify extensions to reduce the odds of an accidental collision.  However, outside of experimental scenarios, I think we could reasonably assume that a peer negotiating a given extension either intends to support it or is somehow malicious.

Type #2 would benefit from a frame that effectively NACKs extension support; a smarter peer could quash these optimistic extensions for the remainder of the session.  However, having called out these two types of extensions, I don’t see this helping with #1.  It’s a related conversation, but not the same.

Matthew, random idea for your specific case….  There was a proposed extension a while ago for sending a hash of the content in one direction or the other.  If you really want a sentinel, you could require the client to return some very lightweight hash of the data, and error if it’s not received in a timely manner.  I don’t know that I’d recommend that, but it’s an option.

From: [email-masked] [mailto:[email-masked]] On Behalf Of Matthew Kerwin
Sent: Tuesday, November 3, 2015 1:53 PM
To: Amos Jeffries <[email-masked]>
Cc: [email-masked]
Subject: Re: can h2 extension frames carry data?

On 3 November 2015 at 14:04, Amos Jeffries <[email-masked]<mailto:[email-masked]>> wrote:

So while it could be a buggy implementation's fault, it could also be that the extension was just written that way. I can send some inconsequential but interesting ​frame* whenever I like, and according to RFC 7540 that's fine; but the proposed RST_STREAM means some recipients might throw an error back at me. So I say again, for an error to work it should be baked into h2 itself. Here and now, the horse has already bolted.

And yes, it is the buggy/broken implementations I'm concerned about. In almost every other part of the protocol, if someone in the wild screws up there's a red flag which we can use to enter an error state. If someone screws up this particular extension, data just evaporates, and we have to rely on other heuristics, like content-length mismatches (which only works if there is a content-length header, and violates layering, etc.), to detect it.

* e.g. ALTSVC

---

From: Willy Tarreau <[email-masked]>
Date: Tue, 3 Nov 2015 07:36:23 +0100

On Tue, Nov 03, 2015 at 02:52:47PM +1000, Matthew Kerwin wrote:
> ???"???Implementations MUST ignore unknown or unsupported values in all
> but interesting ???frame* whenever I like, and according to RFC 7540 that's

Strangely that reminds me a discussion we had on this exact subject
a long time ago when discussion the protocol extensibility. Yes in
theory there is a problem with how the protocol is defined, because
in order to respect the protocol and the obligations that are put on
every actor, you'd need to send your compressed data *in addition* to
the clear data so that those who don't understand compression can
silently drop compressed frames and use the other ones. Obviously
that doesn't make sense.

I think we should classify extension frame types in two categories :
  - the ones that can silently be ignored ;
  - the ones that must be understood and that depend on the extension
    to be correctly understood along the chain (including proxies if
    needed).

This also means that extensions must be classified along these two
categories :
  - those that can be ignored
  - those that may affect the framing or protocol contents and which
    all agents must be careful about (ie if an intermediary doesn't
    know the extension, it must strip it).

Maybe it's not too late to define this classification, implementations
will evolve, you just need to be careful to ensure the fallback for those
not aware of it is nice enough. Typically if we define such classification
and some intermediaries want to inspect contents and are not aware of it,
well... they won't find the data they want in gzipped contents and that's
all. That will give them incentive to implement the classification and/or
support for gzip.

Regards,
Willy

---

From: Cory Benfield <[email-masked]>
Date: Tue, 3 Nov 2015 08:11:21 +0000

> On 3 Nov 2015, at 06:36, Willy Tarreau <[email-masked]> wrote:

How would such a classification work? Add a new registry of frame types that must not be ignored, and then have H2 extensions specify whether their new frames fall into that category? That works, but only if implementations stay up-to-date with it. Any model I can think of where implementations will just naively DTRT requires changing the frame header, and that horse has bolted.

Cory

---

From: Willy Tarreau <[email-masked]>
Date: Tue, 3 Nov 2015 09:54:46 +0100

On Tue, Nov 03, 2015 at 08:11:21AM +0000, Cory Benfield wrote:
> How would such a classification work? Add a new registry of frame types that
> must not be ignored, and then have H2 extensions specify whether their new
> frames fall into that category? That works, but only if implementations stay
> up-to-date with it. Any model I can think of where implementations will just
> naively DTRT requires changing the frame header, and that horse has bolted.

The only way I'm seeing this work is by reserving numeric ranges (for
frame types for example). This means that types within a range or above
or below a certain value are marked as "affects the framing or semantics".
These ones will need to be removed by those who don't support them, and
ACKed by those who accept them. That's more or less what is done with TCP
extensions by the way. Firewalls which didn't support window scaling used
to strip the option from the packets so that it was not negociated ; servers
are asked to respond with it to confirm support. And some options used to
cause some breakage because they were defined long after the initial specs.
Here it's the same but we want to let proxies know that something might
break before it's spec'd to avoid the general breakage.

Willy