From: Amaury Denoyelle Date: Wed, 28 May 2025 09:24:43 +0000 (+0200) Subject: MINOR: h3: support basic HTX start-line conversion into HTTP/3 request X-Git-Tag: v3.3-dev2~49 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7157adb154db7a152b261da8f6471cc2115862c3;p=thirdparty%2Fhaproxy.git MINOR: h3: support basic HTX start-line conversion into HTTP/3 request This commit is the first one of a serie which aim is to implement transcoding of a HTX request into HTTP/3, which is necessary for QUIC backend support. Transcoding is implementing via a new function h3_req_headers_send() when a HTX start-line is parsed. For now, most of the request fields are hardcoded, using a GET method. This will be adjusted in the next following patches. --- diff --git a/include/haproxy/qpack-enc.h b/include/haproxy/qpack-enc.h index 0126937d1..26061a6e0 100644 --- a/include/haproxy/qpack-enc.h +++ b/include/haproxy/qpack-enc.h @@ -1,12 +1,16 @@ #ifndef QPACK_ENC_H_ #define QPACK_ENC_H_ +#include #include struct buffer; int qpack_encode_field_section_line(struct buffer *out); int qpack_encode_int_status(struct buffer *out, unsigned int status); +int qpack_encode_method(struct buffer *out, enum http_meth_t meth); +int qpack_encode_scheme(struct buffer *out); +int qpack_encode_path(struct buffer *out, const struct ist path); int qpack_encode_header(struct buffer *out, const struct ist n, const struct ist v); #endif /* QPACK_ENC_H_ */ diff --git a/src/h3.c b/src/h3.c index 18baf8fba..ccf6eae7a 100644 --- a/src/h3.c +++ b/src/h3.c @@ -1691,6 +1691,74 @@ static int h3_encode_header(struct buffer *buf, return qpack_encode_header(buf, n, v_strip); } +/* Convert a HTX start-line and associated headers stored in into a + * HTTP/3 HEADERS request frame. HTX blocks are removed up to end-of-trailer + * included. + * + * Returns the amount of consumed bytes from buffer or a negative error + * code. + */ +static int h3_req_headers_send(struct qcs *qcs, struct htx *htx) +{ + struct buffer outbuf; + struct buffer headers_buf = BUF_NULL; + struct buffer *res; + struct htx_blk *blk; + enum htx_blk_type type; + int frame_length_size; /* size in bytes of frame length varint field */ + int ret, err; + + res = qcc_get_stream_txbuf(qcs, &err, 0); + BUG_ON(!res); + + b_reset(&outbuf); + outbuf = b_make(b_tail(res), b_contig_space(res), 0, 0); + /* Start the headers after frame type + length */ + headers_buf = b_make(b_head(res) + 5, b_size(res) - 5, 0, 0); + + if (qpack_encode_field_section_line(&headers_buf)) + goto err; + + /* :method */ + if (qpack_encode_method(&headers_buf, HTTP_METH_GET)) + goto err; + /* :scheme */ + + if (qpack_encode_scheme(&headers_buf)) + goto err; + + if (qpack_encode_path(&headers_buf, ist('/'))) + goto err; + + /* :authority */ + if (h3_encode_header(&headers_buf, ist(":authority"), ist("127.0.0.1:20443"))) + goto err; + + /* Now that all headers are encoded, we are certain that res buffer is + * big enough + */ + frame_length_size = quic_int_getsize(b_data(&headers_buf)); + res->head += 4 - frame_length_size; + b_putchr(res, 0x01); /* h3 HEADERS frame type */ + b_quic_enc_int(res, b_data(&headers_buf), 0); + b_add(res, b_data(&headers_buf)); + + ret = 0; + blk = htx_get_head_blk(htx); + while (blk) { + type = htx_get_blk_type(blk); + ret += htx_get_blksz(blk); + blk = htx_remove_blk(htx, blk); + if (type == HTX_BLK_EOH) + break; + } + + return ret; + + err: + return -1; +} + /* Convert a HTX status-line and associated headers stored into into a * HTTP/3 HEADERS response frame. HTX blocks are removed up to end-of-trailer * included. @@ -2191,6 +2259,16 @@ static size_t h3_snd_buf(struct qcs *qcs, struct buffer *buf, size_t count) BUG_ON(btype == HTX_BLK_REQ_SL); switch (btype) { + case HTX_BLK_REQ_SL: + ret = h3_req_headers_send(qcs, htx); + if (ret > 0) { + total += ret; + count -= ret; + if (ret < bsize) + goto out; + } + break; + case HTX_BLK_RES_SL: /* start-line -> HEADERS h3 frame */ ret = h3_resp_headers_send(qcs, htx); diff --git a/src/qpack-enc.c b/src/qpack-enc.c index 006f1f1a9..6c5fd9fb3 100644 --- a/src/qpack-enc.c +++ b/src/qpack-enc.c @@ -132,6 +132,63 @@ int qpack_encode_int_status(struct buffer *out, unsigned int status) return 0; } +/* Returns 0 on success else non-zero. */ +int qpack_encode_method(struct buffer *out, enum http_meth_t meth) +{ + int size, idx = 0; + + switch (meth) { + case HTTP_METH_GET: idx = 17; break; + default: ABORT_NOW(); + } + + if (idx) { + /* method present in QPACK static table + * -> indexed field line + */ + size = qpack_get_prefix_int_size(idx, 6); + if (b_room(out) < size) + return 1; + + qpack_encode_prefix_integer(out, idx, 6, 0xc0); + } + else { + ABORT_NOW(); + } + + return 0; +} + +/* Encode pseudo-header scheme defined to https on buffer. + * + * Returns 0 on success else non-zero. + */ +int qpack_encode_scheme(struct buffer *out) +{ + if (b_room(out) < 2) + return 1; + + /* :scheme: https */ + qpack_encode_prefix_integer(out, 23, 6, 0xc0); + return 0; +} + +/* Returns 0 on success else non-zero. */ +int qpack_encode_path(struct buffer *out, const struct ist path) +{ + if (unlikely(isteq(path, ist("/")))) { + if (!b_room(out)) + return 1; + + qpack_encode_prefix_integer(out, 1, 6, 0xc0); + return 0; + } + else { + /* TODO */ + ABORT_NOW(); + } +} + /* Returns 0 on success else non-zero. */ int qpack_encode_field_section_line(struct buffer *out) {