- From: Michael Sweet <msweet@apple.com>
- Date: Mon, 21 Jul 2014 09:16:10 -0400
- To: Roberto Peon <grmocg@gmail.com>
- Cc: Jason Greene <jason.greene@redhat.com>, David Krauss <potswa@gmail.com>, Greg Wilkins <gregw@intalio.com>, HTTP Working Group <ietf-http-wg@w3.org>, Mark Nottingham <mnot@mnot.net>
- Message-id: <0234CBD3-290F-4BE1-AD89-29C07486D191@apple.com>
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 <grmocg@gmail.com> wrote: > > > > On Sat, Jul 19, 2014 at 9:33 PM, Jason Greene <jason.greene@redhat.com> wrote: > > On Jul 19, 2014, at 10:23 PM, Roberto Peon <grmocg@gmail.com> wrote: > > > > > > > > > On Sat, Jul 19, 2014 at 7:39 PM, Jason Greene <jason.greene@redhat.com> wrote: > > > > On Jul 19, 2014, at 3:38 PM, Roberto Peon <grmocg@gmail.com> wrote: > > > > > > On Sat, Jul 19, 2014 at 10:28 AM, Jason Greene <jason.greene@redhat.com> 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. > > > > Yup, you did say it was compressed... > > 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. > > > 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..) > > > > > > > >> - Intermediary never sees a request, able to work on other workloads > > >> - Origin never sees a request, able to work on other workloads > > > > > >> 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. > > > > 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. > > > > > > > > > > > > >> 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 > > > > > > > > >> 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. > > 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. > > > > > > >> 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. > > > > > 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). > > 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 _________________________________________________________ Michael Sweet, Senior Printing System Engineer, PWG Chair
Received on Monday, 21 July 2014 13:16:47 UTC