From cb985a4da69267df97d31af252b0947aa2181dd7 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Mon, 7 Oct 2019 16:56:34 +0200 Subject: [PATCH] MEDIUM: mux-h2: support emitting CONTINUATION frames after HEADERS There are some reports of users not being able to pass "enterprise" traffic through haproxy when using H2 because it doesn't emit CONTINUATION frames and as such is limited to headers no longer than the negociated max-frame-size which usually is 16 kB. This patch implements support form emitting CONTINUATION when a HEADERS frame cannot fit within a limit of mfs. It does this by first filling a buffer-wise frame, then truncating it starting from the tail to append CONTINUATION frames. This makes sure that we can truncate on any byte without being forced to stop on a header boundary, and ensures that the common case (no fragmentation) doesn't add any extra cost. By moving the tail first we make sure that each byte is moved only once, thus the performance impact remains negligible. This addresses github issue #249. --- src/mux_h2.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/src/mux_h2.c b/src/mux_h2.c index fc4b02f636..00d257f17b 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -1170,6 +1170,54 @@ static inline __maybe_unused int h2_get_frame_hdr(struct buffer *b, struct h2_fh return ret; } + +/* try to fragment the headers frame present at the beginning of buffer , + * enforcing a limit of bytes per frame. Returns 0 on failure, 1 on + * success. Typical causes of failure include a buffer not large enough to + * add extra frame headers. The existing frame size is read in the current + * frame. Its EH flag will be cleared if CONTINUATION frames need to be added, + * and its length will be adjusted. The stream ID for continuation frames will + * be copied from the initial frame's. + */ +static int h2_fragment_headers(struct buffer *b, uint32_t mfs) +{ + size_t remain = b->data - 9; + int extra_frames = (remain - 1) / mfs; + size_t fsize; + char *fptr; + int frame; + + if (b->data <= mfs + 9) + return 1; + + /* Too large a frame, we need to fragment it using CONTINUATION + * frames. We start from the end and move tails as needed. + */ + if (b->data + extra_frames * 9 > b->size) + return 0; + + for (frame = extra_frames; frame; frame--) { + fsize = ((remain - 1) % mfs) + 1; + remain -= fsize; + + /* move data */ + fptr = b->area + 9 + remain + (frame - 1) * 9; + memmove(fptr + 9, b->area + 9 + remain, fsize); + b->data += 9; + + /* write new frame header */ + h2_set_frame_size(fptr, fsize); + fptr[3] = H2_FT_CONTINUATION; + fptr[4] = (frame == extra_frames) ? H2_F_HEADERS_END_HEADERS : 0; + write_n32(fptr + 5, read_n32(b->area + 5)); + } + + b->area[4] &= ~H2_F_HEADERS_END_HEADERS; + h2_set_frame_size(b->area, remain); + return 1; +} + + /* marks stream as CLOSED and decrement the number of active streams for * its connection if the stream was not yet closed. Please use this exclusively * before closing a stream to ensure stream count is well maintained. @@ -4623,6 +4671,18 @@ static size_t h2s_frt_make_resp_headers(struct h2s *h2s, struct htx *htx) } } + /* update the frame's size */ + h2_set_frame_size(outbuf.area, outbuf.data - 9); + + if (outbuf.data > h2c->mfs + 9) { + if (!h2_fragment_headers(&outbuf, h2c->mfs)) { + /* output full */ + if (b_space_wraps(mbuf)) + goto realign_again; + goto full; + } + } + /* we may need to add END_STREAM except for 1xx responses. * FIXME: we should also set it when we know for sure that the * content-length is zero as well as on 204/304 @@ -4634,9 +4694,6 @@ static size_t h2s_frt_make_resp_headers(struct h2s *h2s, struct htx *htx) if (!h2s->cs || h2s->cs->flags & CS_FL_SHW) es_now = 1; - /* update the frame's size */ - h2_set_frame_size(outbuf.area, outbuf.data - 9); - if (es_now) outbuf.area[4] |= H2_F_HEADERS_END_STREAM; @@ -4912,6 +4969,18 @@ static size_t h2s_bck_make_req_headers(struct h2s *h2s, struct htx *htx) } } + /* update the frame's size */ + h2_set_frame_size(outbuf.area, outbuf.data - 9); + + if (outbuf.data > h2c->mfs + 9) { + if (!h2_fragment_headers(&outbuf, h2c->mfs)) { + /* output full */ + if (b_space_wraps(mbuf)) + goto realign_again; + goto full; + } + } + /* we may need to add END_STREAM if we have no body : * - request already closed, or : * - no transfer-encoding, and : @@ -4927,9 +4996,6 @@ static size_t h2s_bck_make_req_headers(struct h2s *h2s, struct htx *htx) if (!h2s->cs || h2s->cs->flags & CS_FL_SHW) es_now = 1; - /* update the frame's size */ - h2_set_frame_size(outbuf.area, outbuf.data - 9); - if (es_now) outbuf.area[4] |= H2_F_HEADERS_END_STREAM; -- 2.39.5