MEDIUM: h2: basic processing of HEADERS frame

This takes care of creating a new h2s and a new conn_stream when a
HEADERS frame arrives. The recv() callback from the data layer is then
called to extract the frame into the stream's buffer. It is verified
that the stream ID is strictly greater than the known max stream ID.
And the last_id is updated if the current request is properly converted.
The streams are created in open or half-closed(remote) states.

For now there are some limitations :
  - frames without END_HEADERS are rejected (CONTINUATION not supported
    yet, will require some more changes so that the stream processor
    checks the H2 frame header by itself and steals the frames from the
    connection)
  - padding/stream_dep/priority are currently ignored
  - limited error handling, could be improved

But at least the request is properly decoded, transcoded and processed.
This commit is contained in:
Willy Tarreau 2017-10-13 19:23:14 +02:00
parent 45f752e037
commit 13278b44b1

View File

@ -13,6 +13,7 @@
#include <common/cfgparse.h>
#include <common/config.h>
#include <common/h2.h>
#include <common/hpack-dec.h>
#include <common/hpack-tbl.h>
#include <common/net_helper.h>
#include <proto/applet.h>
@ -1095,6 +1096,89 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
return 0;
}
/* processes a HEADERS frame. Returns > 0 on success or zero on missing data.
* It may return an error in h2c or h2s. Described in RFC7540#6.2. Most of the
* errors here are reported as connection errors since it's impossible to
* recover from such errors after the compression context has been altered.
*/
static int h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
{
int error;
if (!h2c->dfl) {
error = H2_ERR_PROTOCOL_ERROR; // empty headers frame!
goto strm_err;
}
if (!h2c->dbuf->size)
return 0; // empty buffer
if (h2c->dbuf->i < h2c->dfl && h2c->dbuf->i < h2c->dbuf->size)
return 0; // incomplete frame
/* now either the frame is complete or the buffer is complete */
if (h2s->st != H2_SS_IDLE) {
/* FIXME: stream already exists, this is only allowed for
* trailers (not supported for now).
*/
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) {
/* RFC7540#5.1.1 stream id > prev ones, and must be odd here */
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
h2s = h2c_stream_new(h2c, h2c->dsi);
if (!h2s) {
error = H2_ERR_INTERNAL_ERROR;
goto conn_err;
}
h2s->st = H2_SS_OPEN;
if (h2c->dff & H2_F_HEADERS_END_STREAM) {
h2s->st = H2_SS_HREM;
h2s->flags |= H2_SF_ES_RCVD;
}
/* call the upper layers to process the frame, then let the upper layer
* notify the stream about any change.
*/
h2s->cs->data_cb->recv(h2s->cs);
if (h2s->cs->data_cb->wake(h2s->cs) < 0) {
/* FIXME: cs has already been destroyed, but we have to kill h2s. */
error = H2_ERR_INTERNAL_ERROR;
goto conn_err;
}
if (h2s->st >= H2_SS_RESET) {
/* stream error : send RST_STREAM */
h2c->st0 = H2_CS_FRAME_A;
}
else {
/* update the max stream ID if the request is being processed */
if (h2s->id > h2c->max_id)
h2c->max_id = h2s->id;
}
return 1;
conn_err:
h2c_error(h2c, error);
return 0;
strm_err:
if (h2s) {
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_A;
}
else
h2c_error(h2c, error);
return 0;
}
/* process Rx frames to be demultiplexed */
static void h2_process_demux(struct h2c *h2c)
{
@ -1213,6 +1297,11 @@ static void h2_process_demux(struct h2c *h2c)
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
break;
case H2_FT_HEADERS:
if (h2c->st0 == H2_CS_FRAME_P)
ret = h2c_frt_handle_headers(h2c, h2s);
break;
/* FIXME: implement all supported frame types here */
default:
/* drop frames that we ignore. They may be larger than
@ -1669,14 +1758,153 @@ static void h2_shutw(struct conn_stream *cs, enum cs_shw_mode mode)
{
}
/* Decode the payload of a HEADERS frame and produce the equivalent HTTP/1
* request. Returns the number of bytes emitted if > 0, or 0 if it couldn't
* proceed. Stream errors are reported in h2s->errcode and connection errors
* in h2c->errcode. The caller must already have checked the frame header and
* ensured that the frame was complete or the buffer full.
*/
static int h2_frt_decode_headers(struct h2s *h2s, struct buffer *buf, int count)
{
struct h2c *h2c = h2s->h2c;
const uint8_t *hdrs = (uint8_t *)h2c->dbuf->p;
int flen = h2c->dfl;
int outlen = 0;
int wrap;
int try;
if (!h2c->dfl) {
h2s_error(h2s, H2_ERR_PROTOCOL_ERROR); // empty headers frame!
return 0;
}
/* if the input buffer wraps, take a temporary copy of it (rare) */
wrap = h2c->dbuf->data + h2c->dbuf->size - h2c->dbuf->p;
if (wrap < h2c->dfl) {
char *copy = get_trash_chunk()->str;
memcpy(copy, h2c->dbuf->p, wrap);
memcpy(copy + wrap, h2c->dbuf->data, h2c->dfl - wrap);
hdrs = (uint8_t *)copy;
}
/* The padlen is the first byte before data, and the padding appears
* after data. padlen+data+padding are included in flen.
*/
if (h2c->dff & H2_F_HEADERS_PADDED) {
if (*hdrs >= flen) {
/* RFC7540#6.2 : pad length = length of frame payload or greater */
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
h2c->st0 = H2_SS_ERROR;
return 0;
}
flen -= *hdrs + 1;
hdrs += 1; // skip Pad Length
}
/* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_PRIORITY) {
hdrs += 5; // stream dep = 4, weight = 1
flen -= 5;
}
/* FIXME: lack of END_HEADERS means there's a continuation frame, we
* don't support this for now and can't even decompress so we have to
* break the connection.
*/
if (!(h2c->dff & H2_F_HEADERS_END_HEADERS)) {
h2c_error(h2c, H2_ERR_INTERNAL_ERROR);
return 0;
}
do {
/* first check if we have some room after p+i */
try = buf->data + buf->size - (buf->p + buf->i);
/* otherwise continue between data and p-o */
if (try <= 0) {
try = buf->p - (buf->data + buf->o);
if (try <= 0)
return 0;
}
if (try > count)
try = count;
outlen = hpack_decode_frame(h2c->ddht, hdrs, flen, bi_end(buf), try);
if (outlen == -HPACK_ERR_TOO_LARGE) {
if (buffer_space_wraps(buf)) {
/* it doesn't fit and the buffer is fragmented,
* so let's defragment it and try again.
*/
buffer_slow_realign(buf);
}
else if (buf->o) {
/* need to let the output buffer flush and
* mark the buffer for later wake up.
*/
return 0;
}
else {
/* no other way around */
h2c_error(h2c, H2_ERR_COMPRESSION_ERROR);
return 0;
}
}
else if (outlen < 0) {
h2c_error(h2c, H2_ERR_COMPRESSION_ERROR);
return 0;
}
} while (outlen < 0);
/* now consume the input data */
bi_del(h2c->dbuf, h2c->dfl);
h2c->st0 = H2_CS_FRAME_H;
buf->i += outlen;
/* don't send it before returning data!
* FIXME: should we instead try to send it much later, after the
* response ? This would require that we keep a copy of it in h2s.
*/
if (h2c->dff & H2_F_HEADERS_END_STREAM) {
h2s->cs->flags |= CS_FL_EOS;
h2s->flags |= H2_SF_ES_RCVD;
}
return outlen;
}
/*
* Called from the upper layer, to get more data
* Called from the upper layer to get more data, up to <count> bytes. The
* caller is responsible for never asking for more data than what is available
* in the buffer.
*/
static int h2_rcv_buf(struct conn_stream *cs, struct buffer *buf, int count)
{
/* FIXME: not handled for now */
cs->flags |= CS_FL_ERROR;
return 0;
struct h2s *h2s = cs->ctx;
struct h2c *h2c = h2s->h2c;
int ret = 0;
if (h2c->st0 != H2_CS_FRAME_P)
return 0; // no pre-parsed frame yet
if (h2c->dsi != h2s->id)
return 0; // not for us
if (!h2c->dbuf->size)
return 0; // empty buffer
if (h2c->dbuf->i < h2c->dfl && h2c->dbuf->i < h2c->dbuf->size)
return 0; // incomplete input frame
switch (h2c->dft) {
case H2_FT_HEADERS:
ret = h2_frt_decode_headers(h2s, buf, count);
break;
default:
ret = 0;
}
return ret;
}
/* Called from the upper layer, to send data */