Subject: Cost analysis: (was: Getting to Consensus: CONTINUATION-related issues)

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Jul 2014 11:24:22 -0700

On 18 July 2014 10:57, Jason Greene <[email-masked]> wrote:
> It’s extra complexity, but the implementation isn’t difficult (a cake walk compared to other aspects of the spec). I can certainly appreciate the perspective from implementations that don’t want to touch their code though.

I realize that this is a standard sophist technique in this forum, but
I find that selectively trivializing various aspects of the space
isn't particularly constructive.  Let's try to be even-handed in our
analysis.

On the one side:

CONTINUATION has a cost in code complexity.  It defers the discovery
of what might be a surprisingly large amount of state.

On the other:

A hard cap on size (i.e., option A) has a cost in code complexity.  It
requires that encoders be prepared to double their state commitment so
that they can roll back their header table when the cap is hit.  When
you consider compression, it does not prevent there from being a
surprisingly large quantity of header information.

Option B seems almost orthogonal to these points, providing a 431
warning.  I'm not really certain how it would be used.

Did I miss anything else pertinent to the discussion?

---

From: Michael Sweet <[email-masked]>
Date: Fri, 18 Jul 2014 15:10:18 -0400

Martin,

> surprisingly large quantity of header information.

Actually, there is a simple solution to this problem - add a flag causes the header table to be reset/cleared before the frame is processed.  The sender can set the flag when it has tried preparing a HEADERS frame and made one too big, or when it gets a 431 response that indicates the recipient was unable to process it fully.

That solves the header-table-is-out-of-sync and allows the sender to maintain a single header table should that become too much of a burden (although constrained endpoints can always specify settings to disable the header table on their end and always use encoding that doesn't add values to the header table...)

_________________________________________________________
Michael Sweet, Senior Printing System Engineer, PWG Chair

---

From: Willy Tarreau <[email-masked]>
Date: Fri, 18 Jul 2014 21:13:22 +0200

Hi Martin,

On Fri, Jul 18, 2014 at 11:24:22AM -0700, Martin Thomson wrote:
> > It???s extra complexity, but the implementation isn???t difficult (a cake walk compared to other aspects of the spec). I can certainly appreciate the perspective from implementations that don???t want to touch their code though.
> 

Option B doesn't state that CONTINUATION are only used after a full
header frame so it does not completely solve the issue. If it offered
that provision, implementations could decide that upon receiving CONT
they respond with a 431. That would discourage abusing CONT and sending
too large headers (that would lead to the situation we have in 1.1 where
interoperability is rather good overall).

Willy

---

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Jul 2014 12:16:42 -0700

On 18 July 2014 12:13, Willy Tarreau <[email-masked]> wrote:
> header frame so it does not completely solve the issue.

It doesn't.  I don't believe that option is on the table.

---

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Jul 2014 12:17:39 -0700

> That solves the header-table-is-out-of-sync and allows the sender to maintain a single header table should that become too much of a burden (although constrained endpoints can always specify settin…

We do already have that capability.

---

From: Jason Greene <[email-masked]>
Date: Fri, 18 Jul 2014 14:25:29 -0500

> isn't particularly constructive.  Let's try to be even-handed in our

Fair enough, although I really didn’t think I said anything controversial. I simply meant that even if you agree with me that the code complexity is marginal, I understand the reluctance to touch already developed and working HPACK routines.

> of what might be a surprisingly large amount of state.

Right and there is CPU + memory + transmit cost for both parties in the conversation vs raw sender compute time.

> surprisingly large quantity of header information.

One caveat:

A client that *chooses* to send more than max_header_frame (lets assume 16KB) uncompressed headers, does need an additional 4KB of temporary space to go along with the 16KB destination buffer. So the rollback means you need 20KB vs 16KB.

I emphasize the choose part because clients that really care about this extra 4k can still send 99.8% of headers by simply treating the compressed limit as the uncompressed limit, and thereby leaving their hpack routines untouched.

> 
> Option B seems almost orthogonal to these points,

Yes, I agree that this only affects A.

> providing a 431

An intermediary can make accurate determinations about what to relay and what not to. This saves network bandwidth and mitigates the impact of HOL blocking when connection coalescing is occurring, while also avoiding arbitrary cut-offs that drop what should be perfectly acceptable data.  

> Did I miss anything else pertinent to the discussion?

Nope I think you got it all.

--
Jason T. Greene
WildFly Lead / JBoss EAP Platform Architect
JBoss, a division of Red Hat

---

From: Michael Sweet <[email-masked]>
Date: Fri, 18 Jul 2014 15:28:39 -0400

Martin,

> We do already have that capability.

I know we have a setting for the max header table size, and the recipient can clear the sender's header table by setting the max header table size to 0, but I don't see a way for the sender to clear the recipient's header table - that would be necessary to reset things if the sender did not send the HEADERS frame to the recipient and was unable to revert the header table to its previous state.

---

From: Jason Greene <[email-masked]>
Date: Fri, 18 Jul 2014 14:37:57 -0500

> I know we have a setting for the max header table size, and the recipient can clear the sender's header table by setting the max header table size to 0, but I don't see a way for the sender to clea…

I think he’s referring to the encoding context update instruction in HPACK itself. So I guess you could just set the table to 0 and back to 4096 in the next HEADERS frame.

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Fri, 18 Jul 2014 19:37:23 +0000

In message <CABkgnnWmBUNKFDH8JKz8GKRgZDaS=1f6yQ0C6CdF_zv=[email-masked]>
, Martin Thomson writes:

>isn't particularly constructive.

I agree.  Misinformation is also bad.

>of what might be a surprisingly large amount of state.

And even though CONTINUATION in themselves do not imply or cause
any limit to exist, all implementations are going to have limits,
one way or another.  What these limits might be is anyones guess
at this point, but HTTP/1 deployments might be indicative.

Reception of CONTINUATION frames carries a cost in complexity for
memory and buffer management, independent of there being limits or
not.

CONTINUATIONS are significantly more complext to describe in the
draft (compare red/green in the Greg at all draft).

>On the other:
>
>A hard cap on size (i.e., option A) has a cost in code complexity.

I pressume you mean ... over option B) ?

If so, it will be quite the contrary:  Both senders and receivers
will have much simpler memory management and far less state to keep
track of with complete header-sets in a single frame.

>It requires that encoders be prepared to double their state commitment so
>that they can roll back their header table when the cap is hit.

No, it doesn't, the encoders can just sacrifice the connection and
open another, which will be an incredibly common implementation
because header-sets larger than the other side is willing to accept
are going to be incredibly rare, and primarily caused by attacks.

(See my previous analysis of the scenarios where larger than max
could be encountered.  Please respond relative to that analysis
if you disagree with it.)

-- 
Poul-Henning Kamp       | UNIX since Zilog Zeus 3.20
[email-masked]         | TCP/IP since RFC 956
FreeBSD committer       | BSD since 4.3-tahoe    
Never attribute to malice what can adequately be explained by incompetence.

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 18 Jul 2014 22:37:51 +0300

On Fri, Jul 18, 2014 at 03:28:39PM -0400, Michael Sweet wrote:
> Martin,
> 
> I know we have a setting for the max header table size, and the
> recipient can clear the sender's header table by setting the max
> header table size to 0, but I don't see a way for the sender to
> clear the recipient's header table - that would be necessary to
> reset things if the sender did not send the HEADERS frame to the
> recipient and was unable to revert the header table to its previous
> state.

See section 4.4 in -07.

"Maximum Header Table Size Change".

-Ilari

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Fri, 18 Jul 2014 19:38:49 +0000

In message <[email-masked]>, Michael Sweet writ
es:

>Actually, there is a simple solution to this problem - add a flag causes
>the header table to be reset/cleared before the frame is processed. 

+1

(We should do this no matter what, since it can also be used as way
to dump sensitive data from the decompressor-state on the other side.)

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Fri, 18 Jul 2014 19:40:00 +0000

In message <[email-masked]>

>We do already have that capability.

As far as I remember, only with respect the the reference set,
not with respect to the dynamic set ?

---

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Jul 2014 12:46:55 -0700

> not with respect to the dynamic set ?

It looks a little like this:

0x20 0x2f (some number) ...

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Fri, 18 Jul 2014 19:46:24 +0000

>"Maximum Header Table Size Change".

Ok, I missed that one, and I'm clearly not alone.

Can we please add text to explain this is how you reset state on
the far side ?

---

From: Michael Sweet <[email-masked]>
Date: Fri, 18 Jul 2014 16:07:52 -0400

OK, so that allows the sender to set the header table size to 0, but not to clear it and leave the size alone...

---

From: Michael Sweet <[email-masked]>
Date: Fri, 18 Jul 2014 16:13:47 -0400

Jason,

On Jul 18, 2014, at 3:37 PM, Jason Greene <[email-masked]> wrote:
> ...

OK, I read that section wrong then - it sounded like you could only reduce the size, not put it back up to the recipient's original limit.  Re-reading it now it makes sense; should this be stated explicitly in the HTTP/2 spec as the recommended re-synchronization procedure?

---

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Jul 2014 13:30:52 -0700

> OK, so that allows the sender to set the header table size to 0, but not to clear it and leave the size alone...

Set size to zero.  Set size back.

---

From: Greg Wilkins <[email-masked]>
Date: Sat, 19 Jul 2014 10:31:16 +1000

> Did I miss anything else pertinent to the discussion?

Yes - the option c - namely the "drop continuations and fragment headers
proposal" in the wiki.

It avoids the role back issues of a).  Can work with either a compressed
header limit, an uncompressed header limit or no header limit. supports
streaming headers & unlimited headers.  It can be interleaved if we wish
and while that may slightly increase the DoS surface, it is at a big gain
in protection from HOL blocking and multiplexing quality of service, plus
it does not require interleaving support - just allows it.

I really think it was the best proposal that consider all the 'can't live
with' points raised on this subject.  But it really did not get a fair
consideration (not blaming the chair for that - I saw the same lack of
enthusiasm as he did in the WG).

It is a fine mess that we find ourselves in!

cheers

-- 
Greg Wilkins <[email-masked]>
http://eclipse.org/jetty HTTP, SPDY, Websocket server and client that scales
http://www.webtide.com  advice and support for jetty and cometd.

---

From: Jason Greene <[email-masked]>
Date: Fri, 18 Jul 2014 20:10:27 -0500

> OK, I read that section wrong then - it sounded like you could only reduce the size, not put it back up to the recipient's original limit.  Re-reading it now it makes sense; should this be stated e…

I added this info to the wiki as one of 3 options on dealing with compresses limits:
https://github.com/http2/http2-spec/wiki/ContinuationProposals#dealing-with-compressed-limits

---

From: Amos Jeffries <[email-masked]>
Date: Sat, 19 Jul 2014 15:10:30 +1200

> are going to be incredibly rare, and primarily caused by attacks.

That connection behavour is severe overkill. The rollback argument is a
strawman.

HPACK compresssed size can be calculated as frame_length < sum(2+
header_size) where header_size is the length of an individual header in
HTTP/1.1 size, and all header values in static table redux to '1'.
Under option A senders are required to buffer up to N bytes of header
un-compressed (their choice of N), plus a buffer of same size for
compressing into.

If the un-compressed version exceeds what the local end is willing to
buffer OR the above formula output exceeds what the remote end is
willing to receive - then the frame does not need to be compressed at
all. Ergo, no rollback required.

What *is* required is that buffer space to hold the un-compressed
headers AND incompletely compressed headers simultaneously during
compression.

The solution to this appears to be allowing senders to output estimate
frames size as frame length value then pad with suffix octets up to that
if it somehow compresses smaller than estimated. Such frames could be
streamed directly to the recipient with specific size up front and no
additionanl buffer requirements.

Amos

---

From: Roberto Peon <[email-masked]>
Date: Fri, 18 Jul 2014 21:19:41 -0700

> un-compressed (their choice of N), plus a buffer of same size for
A 'compressed' header could be larger than the original (4 times larger for
some values), or it could be much smaller.
I don't follow your calculation.

> all. Ergo, no rollback required.
It implies that a rollback is not required if one is willing to not
compress in a large number of cases where compression would have been
highly effective.

> headers AND incompletely compressed headers simultaneously during
And it requires that we destroy the value proposition of compression,
assuming we do it as suggested here, since there will be a large percentage
of the time where we'd not compress when compressing would have been
successful.

> additionanl buffer requirements.

The whole point of compression is to send fewer bytes, and with this
proposition, we're almost always guaranteed not to do that.

-=R

---

From: Greg Wilkins <[email-masked]>
Date: Sat, 19 Jul 2014 16:11:38 +1000

Roberto,

With regard to roll back, I see no difference between the burden of roll
back in the sender vs the burden of enforced decoding in the receiver.   Ie
we currently have the issue that when a header limit is breached then the
receiver has to either continue to process anyway or discard the entire
connection.      By moving a compressed limit check to the sender, the
choice is much the same - roll back or discard the entire connection.

Moving the limit from receiver to sender makes this problem more explicit -
but it does not create the problem, which exists any way. Fundamentally any
limit that is applied after the encoding has started is too late - no
matter if it is in the sender or the receiver. Once encoding has started,
then you either have to continue to process the headers or discard the
entire connection.

The only way to efficiently handle a limit is to have it as a declared
uncompressed limit enforced by the sender before encoding starts. Only then
can failure be determine before committing to the entire
encoding/sending/decoding process.

Note that pretending that there is no limit by not declaring it, does not
solve the problem as there will always be a limit (or a massive DoS
vulnerability). Making the limit undeclared does not avoid the problem that
encoding has started.

In hind site,  I think we should have separated the issues of how do we
send large headers from the orthogonal  issue of how we limit large
headers, which are really orthogonal:

How do we transport large headers?:
a) Large Frames
b) Continuations
c) Fragmented Headers frame

How do we limit the max header size?
x) Expressed as a max compressed size (perhaps == a max frame size)
y) Expressed as a max uncompressed size
z) No declared limit (but receivers may apply a limit with 431 or GO-AWAY)

I think any of the limits can be applied to any of the transports.

Mark - is it too late to re frame the consensus questions?  Have you been
able to see any clarity in the other thread?

For the record my preferences are   c,a,b   &  y,x,z,    but I can live
with all.

cheers

On 19 July 2014 14:19, Roberto Peon <[email-masked]> wrote:

> A 'compressed' header could be larger than the original (4 times larger
> for some values), or it could be much smaller.

---

From: David Krauss <[email-masked]>
Date: Sat, 19 Jul 2014 15:31:23 +0800

On 2014–07–19, at 2:11 PM, Greg Wilkins <[email-masked]> wrote:

> 
> Roberto,
> 
> With regard to roll back, I see no difference between the burden of roll back in the sender vs the burden of enforced decoding in the receiver.   Ie we currently have the issue that when a header limit is breached then the receiver has to either continue to process anyway or discard the entire connection.      By moving a compressed limit check to the sender, the choice is much the same - roll back or discard the entire connection.  

There’s a middle path, which I alluded to in my A/B “vote” message.

Enforced decoding is not a burden on the receiver as long as it implements streaming. An ideal receiver (the QOI essentially required by the current spec) can keep on receiving and forwarding/discarding beyond its limit without committing extra memory or undue CPU.

If we can agree that GOAWAY on excessive headers is good enough for simple implementations, and streaming is reasonable to implement for any application that really doesn’t want to send GOAWAY, then the hard limit should remain at the receiver, with voluntary self-limiting by senders.

> The only way to efficiently handle a limit is to have it as a declared uncompressed limit enforced by the sender before encoding starts. Only then can failure be determine before committing to the entire encoding/sending/decoding process.

A proxy representing servers with different limits has to report the lowest common denominator. A client application may know better that its particular server supports a higher limit. The best outcome requires sending the headers anyway and just seeing whether someone complains.

On the other hand, no evidence has been presented that a server requiring big-header requests has ever been proxyable in the first place.

Limiting compressed data is nonsense. Users and applications can’t really reason about compressed sizes. Having added jumbo frames, pushing that sort of semantic up the abstraction stack is irresponsible. Uncompressed limits are what both application-level endpoints care about.

> Note that pretending that there is no limit by not declaring it, does not solve the problem as there will always be a limit (or a massive DoS vulnerability). Making the limit undeclared does not avoid the problem that encoding has started.

A good server can set a different limit on each individual web app. The DoS potential of headers beyond such a limit is no more than any other garbage that gets thrown in the bit-bucket. Kerberos is bringing the pain upon itself... but to the application software, big request headers are the same as any upload.

> In hind site,  I think we should have separated the issues of how do we send large headers from the orthogonal  issue of how we limit large headers, which are really orthogonal:
> 
> Mark - is it too late to re frame the consensus questions?  Have you been able to see any clarity in the other thread?
> 
> For the record my preferences are   c,a,b   &  y,x,z,    but I can live with all.

Excellent selection, sir.

---

From: Jason Greene <[email-masked]>
Date: Sat, 19 Jul 2014 12:28:19 -0500

> Enforced decoding is not a burden on the receiver as long as it implements streaming. An ideal receiver (the QOI essentially required by the current spec) can keep on receiving and forwarding/disca…
 
I argue the opposite is true.
If you look at a comparison of say a client that sends 1MB of compressed headers, with one intermediary, but with a 16KB frame limit:

Streaming discard approach
--------------------------
- Client hpack encodes and transmits 64x16KB frames
- Intermediary reencodes 64x16KB frames
- Origin decodes and discards 64x16KB frames

Simple Compressed Limit Client 
(treats compressed limit as uncompressed)
-----------------------------------------
- Client compares 1MB to 16KB and rejects the request with no copying, transmitting, or processing
- Intermediary never sees a request, able to work on other workloads
- Origin never sees a request, able to work on other workloads

Compression Efficient Client
----------------------------
- Client compares 1MB to 16KB, and realizes it must copy the state table (4k extra temp mem)
- Client processes until full (likely 32KB of data)
- Intermediary never sees a request, able to work on other workloads
- Origin never sees a request, able to work on other workloads

Reset on Overflow Client
-------------------------
- Client processes until overflow (likely 32KB of data)
- Subsequent request needs a few bytes to reset the table 
- Intermediary never sees a request, able to work on other workloads
- Origin never sees a request, able to work on other workloads

The streaming discard approach has the highest overall cost in computation time for all parties. It also introduces latency since all other streams must wait until the stream has completed. Finally it consumes unnecessary network bandwidth. 

> 
> If we can agree that GOAWAY on excessive headers is good enough for simple implementations,

Dropping the connection is somewhat tolerable for a client to origin topology. However it negatively impacts user experience. It’s problematic when you have intermediaries since a dropped connection potentially affects more traffic than that initiated by the user. 

> and streaming is reasonable to implement for any application that really doesn’t want to send GOAWAY, then the hard limit should remain at the receiver, with voluntary self-limiting by senders.

Voluntary self-limiting does indeed help the problem because an intermediary can prevent relaying and the subsequent GOAWAY.

> A proxy representing servers with different limits has to report the lowest common denominator.

Not necessarily. A proxy could dynamically pick the highest (provided its within tolerable levels) and discard traffic for lower limited origins.  

> A client application may know better that its particular server supports a higher limit. The best outcome requires sending the headers anyway and just seeing whether someone complains.

I don’t follow your argument here. A receiver is always going to be the one to know what its limits are unless it reports incorrect values, which would be a bug.

> On the other hand, no evidence has been presented that a server requiring big-header requests has ever been proxyable in the first place.

Well, there is the gigantic kerberos ticket use case, and those are certainly proxyable today. It’s hard to see how large headers are only appropriate across a single hop vs multiple hops.

> 
> Limiting compressed data is nonsense. Users and applications can’t really reason about compressed sizes.

Sure they can:

> Having added jumbo frames, pushing that sort of semantic up the abstraction stack is irresponsible. Uncompressed limits are what both application-level endpoints care about.

I think its fair to say that looking at uncompressed values are simpler for a sender. 

> A good server can set a different limit on each individual web app. The DoS potential of headers beyond such a limit is no more than any other garbage that gets thrown in the bit-bucket. Kerberos i…

You can’t really determine which app to send the request to until the headers are processed, and partial processing isn’t reliable since we don’t have ordering rules on common selectable data. So the limit makes the most sense at a higher level than the application. This is quite different than an upload which involves passing the request to the application before the upload data is fully consumed, and the application is in control of that processing.

Anyway just to be clear I am fine with both approaches. I am not arguing against the B proposal. I just wanted to address some of the concerns with the client impact A.

---

From: Roberto Peon <[email-masked]>
Date: Sat, 19 Jul 2014 13:38:43 -0700

On Sat, Jul 19, 2014 at 10:28 AM, Jason Greene <[email-masked]>
wrote:

> >> With regard to roll back, I see no difference between the burden of
> roll back in the sender vs the burden of enforced decoding in the receiver.
>   Ie we currently have the issue that when a header limit is breached then
> the receiver has to either continue to process anyway or discard the entire
> > Enforced decoding is not a burden on the receiver as long as it
> implements streaming. An ideal receiver (the QOI essentially required by
> the current spec) can keep on receiving and forwarding/discarding beyond
> its limit without committing extra memory or undue CPU.
>
> If you look at a comparison of say a client that sends 1MB of compressed
> headers, with one intermediary, but with a 16KB frame limit:
>

How does the client know that 1MB cannot compress to 16KB? 1MB *can*
compress to 16kb.
The client must have compressed the header to know if it would or would not
become 16kb.
Either that, or it is guessing, and that would hurt latency, reliability,
and determinism for the substantial number of false-positives it would
force into being.

Again, this is not guaranteed, it is only specified.

> Compression Efficient Client
This leaves out the common case when the state table is copied and there
was no revert needed. That was 4k worth of copying for every request where
no copying was necessary. This is likely to be a substantial expense in the
common case.

Reset on Overflow Client

The intermediary would still likely see a request, at least if it was
forward proxy, as it is very unlikely to know what the limit is when it
just has the request from the user, and doesn't yet have a connection to
the origin.

Of any of the compressed-length options, this one is clearly the best,
though it still doesn't help any of the determinism problems, e.g. where
you can get request 'B' in, but only if it was preceded by request 'A';
request 'B' on a new connection would "mysteriously" fail. The determinism
issue is really quite nasty, imho, and is a strong argument for not using
compressed-length limits.

>
> The streaming discard approach has the highest overall cost in computation
> time for all parties. It also introduces latency since all other streams
> must wait until the stream has completed. Finally it consumes unnecessary
> network bandwidth.
>

In the common case (i.e. ~99.9%) of the time, streaming potentially reduces
latency since one need not wait for the entire set of headers to be encoded
before forwarding. In the hopefully rare case (or else the protocol has
some real interop problems) where the headers exceed the recipient's limit,
you're right, it can increase latency.

> Dropping the connection is somewhat tolerable for a client to origin
> topology. However it negatively impacts user experience. It’s problematic
> when you have intermediaries since a dropped connection potentially affects
> more traffic than that initiated by the user.
>
> > and streaming is reasonable to implement for any application that really
> doesn’t want to send GOAWAY, then the hard limit should remain at the
> receiver, with voluntary self-limiting by senders.
>
> Voluntary self-limiting does indeed help the problem because an
> intermediary can prevent relaying and the subsequent GOAWAY.
>
> >
> Not necessarily. A proxy could dynamically pick the highest (provided its
> within tolerable levels) and discard traffic for lower limited origins.
>
>
... and then the limit fails to offer any supposed savings.

> > A client application may know better that its particular server supports
> a higher limit. The best outcome requires sending the headers anyway and
> just seeing whether someone complains.
>
> I don’t follow your argument here. A receiver is always going to be the
> one to know what its limits are unless it reports incorrect values, which
> would be a bug.
>

This isn't true. A forward proxy must contact a server before it can know
what the server's limit is, thus the client can not know what the limit for
that server would be until after it has sent the message.

>
> >
> > On the other hand, no evidence has been presented that a server
> requiring big-header requests has ever been proxyable in the first place.
>
> Well, there is the gigantic kerberos ticket use case, and those are
> certainly proxyable today. It’s hard to see how large headers are only
> appropriate across a single hop vs multiple hops.
>
> >
> > Having added jumbo frames, pushing that sort of semantic up the
> abstraction stack is irresponsible. Uncompressed limits are what both
> application-level endpoints care about.
>
> >> Note that pretending that there is no limit by not declaring it, does
> not solve the problem as there will always be a limit (or a massive DoS
> > A good server can set a different limit on each individual web app. The
> DoS potential of headers beyond such a limit is no more than any other
> garbage that gets thrown in the bit-bucket. Kerberos is bringing the pain
> upon itself... but to the application software, big request headers are the
> same as any upload.
>
> You can’t really determine which app to send the request to until the
> headers are processed, and partial processing isn’t reliable since we don’t
> have ordering rules on common selectable data. So the limit makes the most
> sense at a higher level than the application. This is quite different than
> an upload which involves passing the request to the application before the
> upload data is fully consumed, and the application is in control of that
> processing.
>
>
This isn't necessarily true-- once one has the headers one needs, one can
choose to make a connection.
For reverse proxies in particular, the receipt of a set of headers on a
particular IP, or with a particular host indication via SNI, the
intermediary can know to whom the connection should be created without
having received *any* of the headers.

Even in the forward-proxy case, all it needs are the ':' headers.

-=R

Anyway just to be clear I am fine with both approaches. I am not arguing
> against the B proposal. I just wanted to address some of the concerns with
> the client impact A.
>
> --
> Jason T. Greene

---

From: Greg Wilkins <[email-masked]>
Date: Sun, 20 Jul 2014 09:51:50 +1000

All,

It looks like there are many use-cases and examples that illustrate how
difficult it can be to apply a limit expressed in compressed bytes.   So
this just further convinces me that the only workable limit is one
expressed in uncompressed bytes and applied by the sender.

I also conclude that not having a limit is not really an option, because
limits do exist even if they are not declared.

So regardless of how large headers are transported, does anybody dispute
these two points?

> having received *any* of the headers.

Note also that h2->h2 load balancers will have less need to inspect headers
as they don't need to stick requests from the same client to the same app
server.  A clients requests will come nicely bundled in a connection

regards

---

From: David Krauss <[email-masked]>
Date: Sun, 20 Jul 2014 09:39:45 +0800

> The streaming discard approach has the highest overall cost in computation time for all parties. It also introduces latency since all other streams must wait until the stream has completed. Finally…

You’re optimizing a failure mode by eliminating functionality (and making it a more common case). The assumption is that the client knows more about the acceptability of its request than the network protocol or the proxy. If the data gets through to the origin, it will probably do something useful.

It’s not a good assumption that lots of headers are just obnoxious and therefore an attack. Header size limit declaration is not realistically going to mitigate DoS.

> Dropping the connection is somewhat tolerable for a client to origin topology. However it negatively impacts user experience. It’s problematic when you have intermediaries since a dropped connectio…

An unfulfilled request will already negatively impact UX, no?

Yes, it would suck for intermediaries, but essentially I’m requiring that cilent-side proxies not be such simple implementations. For reverse proxies, there’s no real difference between a server GOAWAY and a proxy GOAWAY, so it’s feasible again.

> Voluntary self-limiting does indeed help the problem because an intermediary can prevent relaying and the subsequent GOAWAY.

The user-agent should self-limit by default, according to whether the application might call for large requests. Intermediaries should not attempt to constrain the client-server contract. Again, you’re trying to optimize a failure mode.

> I don’t follow your argument here. A receiver is always going to be the one to know what its limits are unless it reports incorrect values, which would be a bug.

Most clients know their origin servers pretty well. The client and server applications are both part of the same whole. Limits are reported by intermediaries in the general case, and cannot be accurate.

> Well, there is the gigantic kerberos ticket use case, and those are certainly proxyable today. It’s hard to see how large headers are only appropriate across a single hop vs multiple hops.

Except all the proxies with header size limits. I didn’t say it’s impossible, anyway, I just said no evidence has been presented.

> https://github.com/http2/http2-spec/wiki/ContinuationProposals#dealing-with-compressed-limits

I don’t think our consensus is going to include rollback, sorry.

An application is a webpage, not a browser or an intermediary. A user in this context is a webmaster, not Mozilla.

> You can’t really determine which app to send the request to until the headers are processed, and partial processing isn’t reliable since we don’t have ordering rules on common selectable data. So the limit makes the most sense at a higher level than the application.

The application is usually at the “top of the stack” and the most abstract so it’s the highest level.

There’s already some mumblings about requiring routing information at the start of a request. This is pretty well required to make proxying/routing work at all, so I wouldn’t worry about particular special cases like this. Large requests with routing at the end are more likely to result in 431 than streamable large requests. Even if it doesn’t make it into the standard, it will be a de-facto requirement.

> This is quite different than an upload which involves passing the request to the application before the upload data is fully consumed, and the application is in control of that processing.
>

---

From: Jason Greene <[email-masked]>
Date: Sat, 19 Jul 2014 21:39:17 -0500

On Jul 19, 2014, at 3:38 PM, Roberto Peon <[email-masked]> wrote:
> 
> On Sat, Jul 19, 2014 at 10:28 AM, Jason Greene <[email-masked]> wrote:
> 
> How does the client know that 1MB cannot compress to 16KB? 1MB *can* compress to 16kb.
> The client must have compressed the header to know if it would or would not become 16kb.
> Either that, or it is guessing, and that would hurt latency, reliability, and determinism for the substantial number of false-positives it would force into being.

My example was with 1MB of compressed state. However, the simple optimization I was referring to is that if the uncompressed state is <= the compressed limit (99.8% case), then the client knows it will fit and need not do anything special. If you have a 0.2% case, then you don’t know and need to one of the various strategies for handling it.

>> Again, this is not guaranteed, it is only specified.

Sure, intentionally bad actors can’t be prevented. Having optimal rules for good players improves general handling and also makes it easier to detect bad actors. 

>> ——————————————
>> This leaves out the common case when the state table is copied and there was no revert needed. > That was 4k worth of copying for every request where no copying was necessary. This is likely > > to be a substantial expense in the common case.

According to the data we have the common case is < 16KB of *uncomrpessed* data, which has no additional overhead. In the case where you do have > 16KB of uncompressed data. Once we are in the 0.2% realm, then yes there is a measurable impact that is potentially wasted. From a memory perspective, assuming 16KB frames, its up to 25% additional overhead. The compute time varies with the number of entries in the table, which I guess the max is 120 with all one byte names and values.   

>> The streaming discard approach has the highest overall cost in computation time for all
>> parties. It also introduces latency since all other streams must wait until the stream has
>> completed. Finally it consumes unnecessary network bandwidth.
> 
> In the common case (i.e. ~99.9%) of the time, streaming potentially reduces latency since one need not wait for the entire set of headers to be encoded before forwarding. In the hopefully rare case (or else the protocol has some real interop problems) where the headers exceed the recipient's limit, you're right, it can increase latency. 

Right in the case where you can fragment the request smaller than the frame size it improves latency, and this is definitely a disadvantage to A

>> Not necessarily. A proxy could dynamically pick the highest (provided its within tolerable 
>> levels) and discard traffic for lower limited origins.
> 
> 

It offers savings up to the limit you set (tolerable levels). So as an example if you have one endpoint that accepts 20K and the other as 16K, you only have a 4K inefficiency. Thats better than no limit.

> 
>>> A client application may know better that its particular server supports a higher limit. The 
>>> best outcome requires sending the headers anyway and just seeing whether someone complains.
> 
>> I don’t follow your argument here. A receiver is always going to be the one to know what its 
>> limits are unless it reports incorrect values, which would be a bug.
> 
> This isn't true. A forward proxy must contact a server before it can know what the server's limit is, thus the client can not know what the limit for that server would be until after it has sent the message.

Well a forward proxy is going to know its limit, which very well could be less than the origin. That is the same today with H1, its just that the limit isn’t communicated. Although I think I understand David’s argument now, which is that the spec implied default could lead to proxies being more restrictive than they were in the past. 

> 
> This isn't necessarily true-- once one has the headers one needs, one can choose to make a connection.
> For reverse proxies in particular, the receipt of a set of headers on a particular IP, or with a particular host indication via SNI, the intermediary can know to whom the connection should be created without having received *any* of the headers.
> 

Ah yes thats true, there are cases where factors other than headers allow selection. I recall discussion of sorting : headers up, but don’t recall the status of that. This is a good reason to do that.

---

From: Roberto Peon <[email-masked]>
Date: Sat, 19 Jul 2014 20:23:35 -0700

On Sat, Jul 19, 2014 at 7:39 PM, Jason Greene <[email-masked]>
wrote:

> > Either that, or it is guessing, and that would hurt latency,
> reliability, and determinism for the substantial number of false-positives
> it would force into being.
>
> My example was with 1MB of compressed state. However, the simple
> optimization I was referring to is that if the uncompressed state is <= the
> compressed limit (99.8% case), then the client knows it will fit and need
> not do anything special. If you have a 0.2% case, then you don’t know and
> need to one of the various strategies for handling it.
>
> Yup, you did say it was compressed...
But how do you know that you have 1MB of compressed state without actually
compressing?

> Sure, intentionally bad actors can’t be prevented. Having optimal rules
> for good players improves general handling and also makes it easier to
> detect bad actors.

But we've asserted that these rules won't be used in the common case, and
also that malicious entities won't respect them!
If so, why are we using these rules? Is the exception where we're talking
about non-malicious clients sending large headers such a big deal?

> >> This leaves out the common case when the state table is copied and
> there was no revert needed. > That was 4k worth of copying for every
> request where no copying was necessary. This is likely > > to be a
> substantial expense in the common case.
>
> According to the data we have the common case is < 16KB of *uncomrpessed*
> data, which has no additional overhead. In the case where you do have >
> 16KB of uncompressed data. Once we are in the 0.2% realm, then yes there is
> a measurable impact that is potentially wasted. From a memory perspective,
> assuming 16KB frames, its up to 25% additional overhead. The compute time
> varies with the number of entries in the table, which I guess the max is
> 120 with all one byte names and values.
>

Sure, a heuristic whereby a copy is made only when the uncompressed data
will reduce overhead in the common case, and will likely cause a connection
reset when it occurs, or a compression reset. Of course, since one had to
actually compress the headers to figure out when one has exceeded the
limit, it still doesn't reduce CPU much for the sender. Smart
implementations may be able to dump the compressor state when this happens.
None of that changes that sometimes a request will get through because it
compressed to less than the limit because of previous requests priming the
compression state, and that sometimes it won't.

> > In the common case (i.e. ~99.9%) of the time, streaming potentially
> reduces latency since one need not wait for the entire set of headers to be
> encoded before forwarding. In the hopefully rare case (or else the protocol
> has some real interop problems) where the headers exceed the recipient's
> limit, you're right, it can increase latency.
>
> Right in the case where you can fragment the request smaller than the
> frame size it improves latency, and this is definitely a disadvantage to A
>
> It offers savings up to the limit you set (tolerable levels). So as an
> example if you have one endpoint that accepts 20K and the other as 16K, you
> only have a 4K inefficiency. Thats better than no limit.
>

Proxies have three options:
1) Configure an effectively infinite limit and drop requests internally
- this allows the most requests to the endpoints.
2) Configure some arbitrary limit
- This implies some requests would fail to go to endpoints that would
happily accept the requests.
3) Do something 'smart', i.e. configure different limits based on priori
knowledge
- Since the proxy can't know a priori to the receipt of a request the place
to which the request was headed, this ends up being the same problem all
over again. The proxy will reject things which the endpoint would have
accepted.

Basically, it doesn't seem like it fosters good interop (*especially* given
that it is non-deterministic).

> one to know what its
> > This isn't true. A forward proxy must contact a server before it can
> know what the server's limit is, thus the client can not know what the
> limit for that server would be until after it has sent the message.
>
> Well a forward proxy is going to know its limit, which very well could be
> less than the origin. That is the same today with H1, its just that the
> limit isn’t communicated. Although I think I understand David’s argument
> now, which is that the spec implied default could lead to proxies being
> more restrictive than they were in the past.
>

Yup. Also, I suspect many proxies have had a limit on requests sizes but
not response sizes...
-=R

> Ah yes thats true, there are cases where factors other than headers allow
> selection. I recall discussion of sorting : headers up, but don’t recall
> the status of that. This is a good reason to do that.
>
> --
> Jason T. Greene

---

From: Jason Greene <[email-masked]>
Date: Sat, 19 Jul 2014 22:43:59 -0500

> It’s not a good assumption that lots of headers are just obnoxious and therefore an attack. Header size limit declaration is not realistically going to mitigate DoS.

That’s certainly not my point, so maybe I just haven’t explained all that well. The whole benefit to A is actually simplifying the common case which is headers < 16KB. That is why I like that option. The limit is cooperative, so obviously it can’t prevent a DOS attack. It does however help determine bad actor, which can be useful in DOS detection code. 

The main reason I have been behind so many proposals involving a length is that my primary concern with h2-13 is that continuations encourage HOL blocking in proxies from compliant actors, and lengths gives us a way to prevent that. There have been other proposals which would also solve the problem (namely allowing interleaving of continuations), but they were rejected.
> An unfulfilled request will already negatively impact UX, no?

Well I mean that it impacts other requests. So if you have some bad bit of JS code that triggers 413, now you get delays loading pages as well.

> Yes, it would suck for intermediaries, but essentially I’m requiring that cilent-side proxies not be such simple implementations. For reverse proxies, there’s no real difference between a server GO…

Yeah I agree the first hop doing a GOAWAY is fine in that it only impacts the one client.

> The user-agent should self-limit by default, according to whether the application might call for large requests. Intermediaries should not attempt to constrain the client-server contract. Again, yo…

I see this argument made a lot in the various discussions. That somehow these proposals are favoring the 0.2%. It’s actually optimizing the 99.8% that can be negatively impacted by the 0.2%. This is a common goal in multiplexing protocol design, establishing some basic level of fairness. 

In any case, intermediaries already do constrain what can be sent over them. The exception of course being proxies that are little more than TCP tunnels.

> Except all the proxies with header size limits. I didn’t say it’s impossible, anyway, I just said no evidence has been presented.

Fair enough. It’s an interesting question to answer as to how prevalent it actually is. I vaguelly recall Mike Bishop mentioning IIS is used with proxying and kerberos tickets. 

> I don’t think our consensus is going to include rollback, sorry.

I don’t think it will either. Can you blame me for trying though? Just think of how nice and clean the spec would be with simple header frames…./me wakes up from dream :)

> There’s already some mumblings about requiring routing information at the start of a request. This is pretty well required to make proxying/routing work at all, so I wouldn’t worry about particular…

Yeah thats a good point.

---

From: Jason Greene <[email-masked]>
Date: Sat, 19 Jul 2014 23:33:58 -0500

On Jul 19, 2014, at 10:23 PM, Roberto Peon <[email-masked]> wrote:

> 
> 
> 
> On Sat, Jul 19, 2014 at 7:39 PM, Jason Greene <[email-masked]> wrote:
> 
> But how do you know that you have 1MB of compressed state without actually compressing?

You definitely don’t know. My example was just a rebuttal to David’s suggestion that a discard on an exceeded limit is more efficient than the extra work a client has to do to drop the request.

If you are making the point that a hop can have a limit that the compressed limit greatly exceeds, then yes I agree that is a problem with compressed limits, and a good argument for uncompressed limits. I still argue though that a compressed limit achieves its goal of optimizing the wire, and the multiplexing. It allows for better compression efficiency at the cost of not catching memory model limits of the actual peer. I honestly don’t care if the limit is uncompressed or compressed because they both contribute to the same result.

> But we've asserted that these rules won't be used in the common case, and also that malicious entities won't respect them!
> If so, why are we using these rules? Is the exception where we're talking about non-malicious clients sending large headers such a big deal?

Thats a fair question. I think a good protocol should expect greedy yet compliant actors to pay a price so that good actors continue to receive a good quality of service. While it won’t prevent a DOS, if a server sees these limits being exceeded, it can choose to take countermeasures quicker than without them.
> > According to the data we have the common case is < 16KB of *uncomrpessed* data, which has no additional overhead. In the case where you do have > 16KB of uncompressed data. Once we are in the 0.2% ?
> > realm, then yes there is a measurable impact that is potentially wasted. From a memory perspective, assuming 16KB frames, its up to 25% additional overhead. The compute time varies with the number of 
> > entries in the table, which I guess the max is 120 with all one byte names and values.
> 
> Sure, a heuristic whereby a copy is made only when the uncompressed data will reduce overhead in the common case, and will likely cause a connection reset when it occurs, or a compression reset. Of course, since one had to actually compress the headers to figure out when one has exceeded the limit, it still doesn't reduce CPU much for the sender. Smart implementations may be able to dump the compressor state when this happens.
> None of that changes that sometimes a request will get through because it compressed to less than the limit because of previous requests priming the compression state, and that sometimes it won’t.

Hmm I don’t follow your reply. If the sender has 15KB of uncompressed headers to send, it can reliably generate a < 16KB compressed header and does not need to handle a rollback. So it’s only when you have > 16KB that this extra work kicks in. If the next hop sets a higher limit, then the same would apply to that limit.

> - This implies some requests would fail to go to endpoints that would happily accept the requests.
> 3) Do something 'smart', i.e. configure different limits based on priori knowledge
> - Since the proxy can't know a priori to the receipt of a request the place to which the request was headed, this ends up being the same problem all over again. The proxy will reject things which the endpoint would have accepted.
> 
> Basically, it doesn't seem like it fosters good interop (*especially* given that it is non-deterministic).

It depends on the type of the proxy. If its a reverse proxy, it has the ability to preconfigure. If its a forward proxy it can have a user configurable limit which specifies the max its willing to tolerate, and advertise lower values as it acquires knowledge. 

> Yup. Also, I suspect many proxies have had a limit on requests sizes but not response sizes…

Yeah that could very well be true.

---

From: Roberto Peon <[email-masked]>
Date: Sat, 19 Jul 2014 22:10:07 -0700

On Sat, Jul 19, 2014 at 9:33 PM, Jason Greene <[email-masked]>
wrote:

> You definitely don’t know. My example was just a rebuttal to David’s
> suggestion that a discard on an exceeded limit is more efficient than the
> extra work a client has to do to drop the request.
>
> If you are making the point that a hop can have a limit that the
> compressed limit greatly exceeds, then yes I agree that is a problem with
> compressed limits, and a good argument for uncompressed limits. I still
> argue though that a compressed limit achieves its goal of optimizing the
> wire, and the multiplexing. It allows for better compression efficiency at
> the cost of not catching memory model limits of the actual peer. I honestly
> don’t care if the limit is uncompressed or compressed because they both
> contribute to the same result.
>
>
I'm making the point that one cannot know the compressed size without
compressing.
.. thus, any limit which depends on the compressed size requires either
guessing (and so is not a precise limit), or requires actually compressing.

In other words, you have two options: Do the work of compression of
compressing sender-side, even when you cannot send, or guess, and reject
headers that would have fit within the limit.
(and then there is the nondeterminsm stuff..)

> > > Sure, intentionally bad actors can’t be prevented. Having optimal
> rules for good players improves general handling and also makes it easier
> to detect bad actors.
> >
> > But we've asserted that these rules won't be used in the common case,
> and also that malicious entities won't respect them!
> > If so, why are we using these rules? Is the exception where we're
> talking about non-malicious clients sending large headers such a big deal?
>
> Thats a fair question. I think a good protocol should expect greedy yet
> compliant actors to pay a price so that good actors continue to receive a
> good quality of service. While it won’t prevent a DOS, if a server sees
> these limits being exceeded, it can choose to take countermeasures quicker
> than without them.
>
>
> >
> > >
> > >
> > > According to the data we have the common case is < 16KB of
> *uncomrpessed* data, which has no additional overhead. In the case where
> you do have > 16KB of uncompressed data. Once we are in the 0.2% ?
> > > realm, then yes there is a measurable impact that is potentially
> wasted. From a memory perspective, assuming 16KB frames, its up to 25%
> additional overhead. The compute time varies with the number of
> > None of that changes that sometimes a request will get through because
> it compressed to less than the limit because of previous requests priming
> the compression state, and that sometimes it won’t.
>
> Hmm I don’t follow your reply. If the sender has 15KB of uncompressed
> headers to send, it can reliably generate a < 16KB compressed header and
> does not need to handle a rollback. So it’s only when you have > 16KB that
> this extra work kicks in. If the next hop sets a higher limit, then the
> same would apply to that limit.
>

Unfortunately, that isn't true: A smart compressor can ensure that the
overhead is constant in the number of headers, but a compressor which
doesn't verify that the huffman encoding is smaller than the original can
and will result in output that is larger than the input.

> > - Since the proxy can't know a priori to the receipt of a request the
> place to which the request was headed, this ends up being the same problem
> all over again. The proxy will reject things which the endpoint would have
> accepted.
> >
> It depends on the type of the proxy. If its a reverse proxy, it has the
> ability to preconfigure. If its a forward proxy it can have a user
> configurable limit which specifies the max its willing to tolerate, and
> advertise lower values as it acquires knowledge.
>

It is far easier from a management perspective to use the current model
whereby the server behind the intermediaries does the right thing. It
undoubtedly does result in some wasted resources, but at least in my
opinion, that is likely to be acceptable instead of needing to debug
clients which cannot access the service(s) because of a change in the
nature of the cookies, requests, etc.

-=R

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Sun, 20 Jul 2014 07:15:00 +0000

In message <CAP+FsNf0zavEWmZzdMpery=[email-masked]>

> How does the client know that 1MB cannot compress to 16KB? 1MB *can*

Thanks for bringing this up:

A 1 megabyte request can indeed be compressed to 16KB, if

A) it is a DoS attack

B) it is utterly demented.  See also A.

or

C) up to 64 requests were spent previously priming the compressor state
   on the other side for this very purpose.  See also A.

Implementors beware.

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Sun, 20 Jul 2014 07:18:56 +0000

In message <CAH_y2NEb8KVTAX5Hx=[email-masked]>

>expressed in uncompressed bytes and applied by the sender.

This would be bad-ish for speed, it forces all proxies to always decompress
(or ignore the limit and hope).

>limits do exist even if they are not declared.

Agree.

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Sun, 20 Jul 2014 07:35:30 +0000

In message <[email-masked]>, Jason Greene writ
es:

>That's certainly not my point, so maybe I just haven't explained all 
>that well. The whole benefit to A is actually simplifying the common 
>case which is headers < 16KB. That is why I like that option. The limit 
>is cooperative, so obviously it can't prevent a DOS attack. It does 
>however help determine bad actor, which can be useful in DOS detection 
>code. 

+1:  Stating the limit up front means that everybody who exceeds it
     better have a really good story.

>The main reason I have been behind so many proposals involving a length 
>is that my primary concern with h2-13 is that continuations encourage 
>HOL blocking in proxies from compliant actors, and lengths gives us a 
>way to prevent that. There have been other proposals which would also 
>solve the problem (namely allowing interleaving of continuations), but 
>they were rejected.

+1

>I see this argument made a lot in the various discussions. That somehow 
>these proposals are favoring the 0.2%. It's actually optimizing the 
>99.8% that can be negatively impacted by the 0.2%. This is a common goal 
>in multiplexing protocol design, establishing some basic level of 
>fairness. 

I'm perfectly happy to cause pain for the 0.2% because in my
considered opinion HTTP headers larger than 16KB means that you are
fundamentally confused about the difference between data and metadata.

But it's not really about fairness to the 0.2%, it is about sensible
security:  Any concession to the 0.2% in the default configurations
is also an open invitation to attackers.

>>>certainly proxyable today. It's hard to see how large headers are only 

Actually that one use case is very much one-hop as far as I know.
Nobody has given any evidence that these kernberos tickets are sent
to at public webservers on the internet, they are only used between
servers which are known to handle them, inside isolated administrative
domains.

---

From: Mark Nottingham <[email-masked]>
Date: Sun, 20 Jul 2014 11:06:07 -0400

Hi Greg,

Sorry for the delay; was in the air.

> Mark - is it too late to re frame the consensus questions?  Have you been able to see any clarity in the other thread?

If you look at how I formulated the question, it was mostly about a/b (c still looks like it doesn’t have broad support), with a bit of wiggle room on x/y/z (and I do think we can answer a/b/c and then tackle x/y/z).

More soon,

--
Mark Nottingham   http://www.mnot.net/

---

From: Greg Wilkins <[email-masked]>
Date: Mon, 21 Jul 2014 12:08:32 +1000

On 21 July 2014 01:06, Mark Nottingham <[email-masked]> wrote:

> If you look at how I formulated the question, it was mostly about a/b (c
> still looks like it doesn’t have broad support), with a bit of wiggle room
> on x/y/z (and I do think we can answer a/b/c and then tackle x/y/z).

Hmm I saw it a little differently.  Most of the discussion was really
related to the impact of a header limit and very little about the transport
of large headers and if they need to be fragmented etc.

However, it does look like there is good support for y, which is really
your b (status quo with regard to large header transport and max header
expressed in uncompressed bytes).     So that's a worthwhile step anyway...
just not sure it is sufficient to quell discontent about continuations.

regards

---

From: Michael Sweet <[email-masked]>
Date: Mon, 21 Jul 2014 09:16:10 -0400

I think perhaps we are too focused on preventing an endpoint from sending headers that are too large (the 0.2% case) and not on interoperability and avoiding dropping the connection at all costs.

A strawman proposal:

1. Have endpoints OPTIONALLY advertise a maximum uncompressed header size limit, above which is guaranteed to fail - this would just be advisory for the sender.

2. Have the maximum compressed header frame size be the same as the max-frame-size setting - all frames are thus treated equally for max frame size.

3. Define how a server responds to headers that are too large (431 + GOAWAY?, close connection)

4. Define how a client responds to headers that are too large (GOAWAY?, close connection)

5. Provide implementation guidance for DoS attack handling to avoid interop issues, e.g., should not disconnect just because of a single too-large header frame, etc.

I think #3 and #4 are the only significant area of discussion - there are ways to reset the header table in both directions, but I worry about receivers being able to detect that the header table they have is the same as what the sender used as the basis of the current HEADERS/PUSH_PROMISE frame.  For example, if a client sends 3 consecutive requests in a row, it may not see the 431 response to the first request in time to correctly encode the subsequent requests.

It may be the simplest course is to require the connection to be torn down, but we might be able to add a "table version" field to HEADERS and PUSH_PROMISE to track which header table was in use by the sender, add the "table version" field to the header table size value that is send in the SETTINGS frame for the receiver to communicate the active version on the receiver, and define a RST_STREAM error code for "wrong header table version" that triggers a re-send.

(I'm not sure whether the complexity of a table versioning scheme is necessary for what should be an infrequent exception...)

On Jul 20, 2014, at 1:10 AM, Roberto Peon <[email-masked]> wrote:

> 
> 
> 
> On Sat, Jul 19, 2014 at 9:33 PM, Jason Greene <[email-masked]> wrote:
> 
> I'm making the point that one cannot know the compressed size without compressing.
> .. thus, any limit which depends on the compressed size requires either guessing (and so is not a precise limit), or requires actually compressing.
> 
> In other words, you have two options: Do the work of compression of compressing sender-side, even when you cannot send, or guess, and reject headers that would have fit within the limit.
> Unfortunately, that isn't true: A smart compressor can ensure that the overhead is constant in the number of headers, but a compressor which doesn't verify that the huffman encoding is smaller than the original can and will result in output that is larger than the input.
>  
> 
> >
> It is far easier from a management perspective to use the current model whereby the server behind the intermediaries does the right thing. It undoubtedly does result in some wasted resources, but at least in my opinion, that is likely to be acceptable instead of needing to debug clients which cannot access the service(s) because of a change in the nature of the cookies, requests, etc.
> 
> -=R

---

From: Mark Nottingham <[email-masked]>
Date: Mon, 21 Jul 2014 09:27:59 -0400

Hi Michael,

> 2. Have the maximum compressed header frame size be the same as the max-frame-size setting - all frames are thus treated equally for max frame size.

I think we’re already there for these two...

> 5. Provide implementation guidance for DoS attack handling to avoid interop issues, e.g., should not disconnect just because of a single too-large header frame, etc.

These I expect the editor to address; if people have suggestions after seeing how Martin incorporates the resolutions, I’m sure he’d be glad to have input.

> It may be the simplest course is to require the connection to be torn down, but we might be able to add a "table version" field to HEADERS and PUSH_PROMISE to track which header table was in use by…

I think “require” is too strong here — there are a number of ways of handling this case, and I don’t see a reason to tie the recipient’s hands. Suggest, on the other hand...

---

From: Martin Thomson <[email-masked]>
Date: Mon, 21 Jul 2014 06:35:04 -0700

> These I expect the editor to address; if people have suggestions after seeing how Martin incorporates the resolutions, I’m sure he’d be glad to have input.

Concrete proposals for Section 10.5.1 are welcome, but I believe that
there is text covering the problem already.

http://http2.github.io/http2-spec/#rfc.section.10.5.1

I've no doubt that this can be improved.  As always, I prefer pull requests.

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 22 Jul 2014 15:29:02 +0300

On Mon, Jul 21, 2014 at 09:16:10AM -0400, Michael Sweet wrote:
> I think perhaps we are too focused on preventing an endpoint from
> sending headers that are too large (the 0.2% case) and not on
> interoperability and avoiding dropping the connection at all costs.
> 
> It may be the simplest course is to require the connection to be
> torn down, but we might be able to add a "table version" field to
> HEADERS and PUSH_PROMISE to track which header table was in use by
> the sender, add the "table version" field to the header table size
> value that is send in the SETTINGS frame for the receiver to
> communicate the active version on the receiver, and define a RST_STREAM
> error code for "wrong header table version" that triggers a re-send.
> 
> (I'm not sure whether the complexity of a table versioning scheme is
> necessary for what should be an infrequent exception...)

I think the following might do the trick already:

- Keep counter of sent non-ack SETTINGS frames (settings_sent), initially 0.
- Keep counter of received SETTINGS ack frames (settings_acked), initially 0.
- Keep recovery threshold (recovery_threshold), initially 0.
- If on receiving HEADERS/PUSH_PROMISE, settings_acked < recovery_threshold, then:
  * reset the stream (REFUSED_STREAM?)
  * Discard the received header block (including any possible CONTINUATION).
  * End processing HEADERS/PUSH_PROMISE.
- Otherwise If HEADERS/PUSH_PROMISE decompression fails, then:
  * Send one SETTINGS block containg:
    1) SETTINGS_HEADER_TABLE_SIZE 0
    2) SETTINGS_HEADER_TABLE_SIZE <whatever>
  * Set recovery_threshold <- settings_sent  (includes the SETTINGS just sent).
  * Discard the rest of the header block (including any possible CONTINUATION).
  * Reset the stream (DECOMPRESSION_ERROR?) or 431 it.
  * End processing HEADERS/PUSH_PROMISE.

-Ilari

---

From: Michael Sweet <[email-masked]>
Date: Tue, 22 Jul 2014 08:36:09 -0400

If we support this a first class RST_STREAM error code is probably justified.

Also, how about sending a single SETTINGS frame with two SETTINGS_HEADER_TABLE_SIZE entries (0 and N)?

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 22 Jul 2014 15:45:31 +0300

On Tue, Jul 22, 2014 at 08:36:09AM -0400, Michael Sweet wrote:
> Also, how about sending a single SETTINGS frame with two
> SETTINGS_HEADER_TABLE_SIZE entries (0 and N)?

That's exactly what I proposed (I checked the spec, seems to be allowed
to send two settings in one frame).

-Ilari

---

From: Martin Thomson <[email-masked]>
Date: Tue, 22 Jul 2014 09:20:13 -0700

> to send two settings in one frame).

Yes, that is specifically allowed (we want to permit stateless
processing of settings).

I'll note that the change needs to be acknowledged in a header block
with an encoding context update.

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Wed, 23 Jul 2014 01:29:31 +0900

> with an encoding context update.
​If 2 SETTINGS_HEADER_TABLE_SIZE is in SETTINGS, say 0 and N in this order,
do we have to encode 2 encoding context update for both 0 and N or for just
N?​

​Best regards,
Tatsuhiro Tsujikawa​

---

From: Martin Thomson <[email-masked]>
Date: Tue, 22 Jul 2014 09:45:03 -0700

On 22 July 2014 09:29, Tatsuhiro Tsujikawa <[email-masked]> wrote:
> If 2 SETTINGS_HEADER_TABLE_SIZE is in SETTINGS, say 0 and N in this order,
> N?

This needs clarification, because it's only implicit currently.  You
need to include the lowest and highest values you have seen in
settings when sending the next frame.

https://github.com/http2/http2-spec/issues/565

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Wed, 23 Jul 2014 09:50:23 +0900

> https://github.com/http2/http2-spec/issues/565

We need to also clarify when first header table size change applies: a)
reception of SETTINGS (and its ACK) or b) encoding context update.
My understanding is a). The receiver of SETTINGS processes the setting
values in the order they appear in a frame and shrinking header table size
necessary. Then it can send encoding context update only for last seen
table size value.  This is possible because receiver of SETTINGS ACK also
applies its pending settings in the same way.  It then validates the
received encoding context update against last seen value and applies table
size again.  This is simpler than multiple encodong context update because
we just renember and validate 1 value instead of 2.

Best regards,
Tatsuhiro Tsujikawa

---

From: Martin Thomson <[email-masked]>
Date: Wed, 23 Jul 2014 10:12:05 -0700

On 22 July 2014 17:50, Tatsuhiro Tsujikawa <[email-masked]> wrote:
> necessary. Then it can send encoding context update only for last seen table
> size value.  This is possible because receiver of SETTINGS ACK also applies
> its pending settings in the same way.  It then validates the received
> encoding context update against last seen value and applies table size
> again.  This is simpler than multiple encodong context update because we
> just renember and validate 1 value instead of 2.

Have a look at the text that I proposed on this
(https://github.com/http2/http2-spec/pull/566).

What you have here isn't 100% correct.  When the size limit changes
multiple names, only two values are relevant:

 - the value that the encoder selects (which cannot be larger than the
decoder-provided size)

 - the lowest value that the decoder advertises

If the latter is smaller than the former, then two encoding context
updates are required.

This is the simplest reduction of the effect of any number of changes.

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Thu, 24 Jul 2014 09:55:53 +0900

> > necessary. Then it can send encoding context update only for last seen
table
> multiple names, only two values are relevant:

I know that 2 values are important.

> This is the simplest reduction of the effect of any number of changes.

So decoder changes table size only when it sees encodong context update? It
works, but it has a disadvantage because decoder has to retain header table
size unchanged until HEADERS/PP are received, which might never arrive.
On the other hand, SETTINGS ACK is guaranteed to arrive (and we have a
timeout for it). If decoder changes header table size, including eviction
to the lowest value it advertised when it received SETTINGS ACK, encoder
only sends its selected value based on the last seen value in SETTINGS
decoder sent. This is because decoder already performed eviction based on
the lowest value and encoding context update comes after SETTINGS ACK.

Best regards,
Tatsuhiro Tsujikawa

---

From: Martin Thomson <[email-masked]>
Date: Thu, 24 Jul 2014 06:12:18 -0700

On 23 July 2014 17:55, Tatsuhiro Tsujikawa <[email-masked]> wrote:
> timeout for it). If decoder changes header table size, including eviction to
> the lowest value it advertised when it received SETTINGS ACK, encoder only
> sends its selected value based on the last seen value in SETTINGS decoder
> sent. This is because decoder already performed eviction based on the lowest
> value and encoding context update comes after SETTINGS ACK.

Yes, I would use SETTINGS ACK as the marker to use for trimming the
header table down, but still require the next header block after that
to include a context update.  That might mean two rounds of eviction
if the encoder chooses to use a smaller table size, but that's the
safest approach.

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Thu, 24 Jul 2014 14:35:01 +0000

In message <[email-masked]>

>if the encoder chooses to use a smaller table size, but that's the

The safest and by far most efficient approach is to have an oopcode
for it.

Why that was felt necessary for the reference set but not for the
dynamic set is a good question.

Resizing the dynamic set to zero and then back up invites the
receivers to complicate their memory management because the most
likely resize of the dynamic set isn't going to be one.

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Fri, 25 Jul 2014 01:21:49 +0900

> if the encoder chooses to use a smaller table size, but that's the

​I understand that we chosen the safest approach.  Thanks for the
clarification.
​
​Best regards,
Tatsuhiro Tsujikawa​

---

From: Mike Bishop <[email-masked]>
Date: Thu, 24 Jul 2014 17:29:18 +0000

There's no opcode because it's the compressor who wants (wanted) to clear the reference set since they know that what they're encoding will be quicker if the set is gone, but it's the decompressor that wants to clear the dynamic set to reduce its memory commitment.  (The compressor can partially discard the contents of the dynamic table any time it wants without need to notify the other side it has done so.)

There are no HPACK opcodes sent from decompressor to compressor, unless you want to mix information across the two directions.  That seems like even more of a complication to me.  SETTINGS and the corresponding ACK are effectively the communication channel from decompressor back to the compressor.

-----Original Message-----
From: Poul-Henning Kamp [mailto:[email-masked]] 
Sent: Thursday, July 24, 2014 7:35 AM
To: Martin Thomson
Cc: Tatsuhiro Tsujikawa; Mark Nottingham; David Krauss; Greg Wilkins; HTTP Working Group; Michael Sweet; Jason Greene; Roberto Peon; Ilari Liusvaara
Subject: Re: Recovery from decompression failure (was: Re: Cost analysis: (was: Getting to Consensus: CONTINUATION-related issues))

, Martin Thomson writes:

>to include a context update.  That might mean two rounds of eviction if 
>the encoder chooses to use a smaller table size, but that's the safest 
>approach.

The safest and by far most efficient approach is to have an oopcode for it.

Why that was felt necessary for the reference set but not for the dynamic set is a good question.

Resizing the dynamic set to zero and then back up invites the receivers to complicate their memory management because the most likely resize of the dynamic set isn't going to be one.

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Thu, 24 Jul 2014 17:36:52 +0000

In message <[email-masked]
.com>, Mike Bishop writes:

>There are no HPACK opcodes sent from decompressor to compressor, [...]

Sorry, I think I misunderstood the case you were talking about.

---

From: Mike Bishop <[email-masked]>
Date: Thu, 24 Jul 2014 17:54:53 +0000

Basically, if an compressor wants to clear the dynamic table, it can just stop using it and discard the actual data from memory.  It can either maintain enough bookkeeping data to track expiration of the items (whatever they were), or it can encode a size of zero followed by a non-zero size which frees it from bookkeeping as well (and frees the memory on the peer).  An opcode that was just a shortcut for reduce to zero, then increase to the current value doesn't seem like it adds much to this direction, though it wouldn't hurt much either.

The more common case that was raised for wanting to clear the dynamic table, though, was the decompressor -- it can't clear it without notice to the other side like the compressor can.  That's why it sends SETTINGS to zero and then back.  There's no opportunity for an opcode to flow in this direction.

So why no opcode?  Superfluous in one direction and not (cleanly) an option in the other.

---

From: Roberto Peon <[email-masked]>
Date: Thu, 24 Jul 2014 10:57:34 -0700

Also, the more opcodes, the smaller the space left over for indices is, and
thus the compressor becomes less efficient.
I'd expect having a compressor which wants to clear the dynamic table is a
relatively rare occurrence. Sacrificing bytes in the common case for this
rare case would be counter productive.
-=R

On Thu, Jul 24, 2014 at 10:54 AM, Mike Bishop <[email-masked]>
wrote:

> Basically, if an compressor wants to clear the dynamic table, it can just
> stop using it and discard the actual data from memory.  It can either
> maintain enough bookkeeping data to track expiration of the items (whatever
> they were), or it can encode a size of zero followed by a non-zero size
> which frees it from bookkeeping as well (and frees the memory on the peer).
>  An opcode that was just a shortcut for reduce to zero, then increase to
> the current value doesn't seem like it adds much to this direction, though
> it wouldn't hurt much either.
>
> The more common case that was raised for wanting to clear the dynamic
> table, though, was the decompressor -- it can't clear it without notice to
> the other side like the compressor can.  That's why it sends SETTINGS to
> zero and then back.  There's no opportunity for an opcode to flow in this
> direction.
>
> Cc: Tatsuhiro Tsujikawa; Mark Nottingham; David Krauss; Greg Wilkins; HTTP
> Working Group; Michael Sweet; Jason Greene; Roberto Peon; Ilari Liusvaara
> Subject: RE: Recovery from decompression failure (was: Re: Cost analysis:
> (was: Getting to Consensus: CONTINUATION-related issues))
>
> There's no opcode because it's the compressor who wants (wanted) to clear
> the reference set since they know that what they're encoding will be
> quicker if the set is gone, but it's the decompressor that wants to clear
> the dynamic set to reduce its memory commitment.  (The compressor can
> partially discard the contents of the dynamic table any time it wants
> without need to notify the other side it has done so.)
>
> There are no HPACK opcodes sent from decompressor to compressor, unless
> you want to mix information across the two directions.  That seems like
> even more of a complication to me.  SETTINGS and the corresponding ACK are
> effectively the communication channel from decompressor back to the
> compressor.
>
> Cc: Tatsuhiro Tsujikawa; Mark Nottingham; David Krauss; Greg Wilkins; HTTP
> Working Group; Michael Sweet; Jason Greene; Roberto Peon; Ilari Liusvaara
> Subject: Re: Recovery from decompression failure (was: Re: Cost analysis:
> (was: Getting to Consensus: CONTINUATION-related issues))
>
> In message <
> [email-masked]>
> , Martin Thomson writes:
>
> Resizing the dynamic set to zero and then back up invites the receivers to
> complicate their memory management because the most likely resize of the
> dynamic set isn't going to be one.
>
> --