]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: h3: support basic HTX start-line conversion into HTTP/3 request
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 28 May 2025 09:24:43 +0000 (11:24 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 16 Jun 2025 16:11:09 +0000 (18:11 +0200)
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.

include/haproxy/qpack-enc.h
src/h3.c
src/qpack-enc.c

index 0126937d1f2b7647649cb651063563eb53e70ece..26061a6e088ab3a046343393ae33b339fe354576 100644 (file)
@@ -1,12 +1,16 @@
 #ifndef QPACK_ENC_H_
 #define QPACK_ENC_H_
 
+#include <haproxy/http-t.h>
 #include <haproxy/istbuf.h>
 
 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_ */
index 18baf8fba6b11b227ef0e469fb281e219e963f02..ccf6eae7a3543d8b6abf9380f1055ee659e8c9d4 100644 (file)
--- 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 <htx> into a
+ * HTTP/3 HEADERS request frame. HTX blocks are removed up to end-of-trailer
+ * included.
+ *
+ * Returns the amount of consumed bytes from <htx> 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 <htx> 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);
index 006f1f1a9749e7d2a5e7de611691e956e68605cb..6c5fd9fb386b4c70ef6263d88585020ee1b0a243 100644 (file)
@@ -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 <out> 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)
 {