From 154bc7f8640824f999378cb40f73bac4b0d7e1f5 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 12 Nov 2021 16:09:54 +0100 Subject: [PATCH] MINOR: quic: support hq-interop Implement a new app_ops layer for quic interop. This layer uses HTTP/0.9 on top of QUIC. Implementation is minimal, with the intent to be able to pass interoperability test suite from https://github.com/marten-seemann/quic-interop-runner. It is instantiated if the negotiated ALPN is "hq-interop". --- Makefile | 3 +- include/haproxy/hq_interop.h | 6 ++ src/hq_interop.c | 132 +++++++++++++++++++++++++++++++++++ src/xprt_quic.c | 4 ++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 include/haproxy/hq_interop.h create mode 100644 src/hq_interop.c diff --git a/Makefile b/Makefile index f5dfed238..c7d6ee98d 100644 --- a/Makefile +++ b/Makefile @@ -594,7 +594,8 @@ endif ifneq ($(USE_QUIC),) OPTIONS_OBJS += src/quic_sock.o src/proto_quic.o src/xprt_quic.o src/quic_tls.o \ src/quic_frame.o src/quic_cc.o src/quic_cc_newreno.o src/mux_quic.o \ - src/cbuf.o src/qpack-dec.o src/qpack-tbl.o src/h3.o src/qpack-enc.o + src/cbuf.o src/qpack-dec.o src/qpack-tbl.o src/h3.o src/qpack-enc.o \ + src/hq_interop.o endif ifneq ($(USE_LUA),) diff --git a/include/haproxy/hq_interop.h b/include/haproxy/hq_interop.h new file mode 100644 index 000000000..eb6ebf6bc --- /dev/null +++ b/include/haproxy/hq_interop.h @@ -0,0 +1,6 @@ +#ifndef _HAPROXY_HQ_INTEROP_H_ +#define _HAPROXY_HQ_INTEROP_H_ + +extern const struct qcc_app_ops hq_interop_ops; + +#endif /* _HAPROXY_HQ_INTEROP_H_ */ diff --git a/src/hq_interop.c b/src/hq_interop.c new file mode 100644 index 000000000..0f878f0b2 --- /dev/null +++ b/src/hq_interop.c @@ -0,0 +1,132 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int hq_interop_decode_qcs(struct qcs *qcs, void *ctx) +{ + struct buffer *rxbuf = &qcs->rx.buf; + struct htx *htx; + struct htx_sl *sl; + struct conn_stream *cs; + struct buffer htx_buf = BUF_NULL; + struct ist path; + char *ptr; + + b_alloc(&htx_buf); + htx = htx_from_buf(&htx_buf); + + /* skip method */ + ptr = b_orig(rxbuf); + while (HTTP_IS_TOKEN(*ptr)) + ++ptr; + BUG_ON(!HTTP_IS_SPHT(*ptr)); + ++ptr; + + /* extract path */ + BUG_ON(HTTP_IS_LWS(*ptr)); + path.ptr = ptr; + while (!HTTP_IS_LWS(*ptr)) + ++ptr; + BUG_ON(!HTTP_IS_LWS(*ptr)); + path.len = ptr - path.ptr; + + sl = htx_add_stline(htx, HTX_BLK_REQ_SL, 0, ist("GET"), path, ist("HTTP/1.0")); + sl->flags |= HTX_SL_F_BODYLESS; + sl->info.req.meth = find_http_meth("GET", 3); + + htx_add_endof(htx, HTX_BLK_EOH); + htx_to_buf(htx, &htx_buf); + + cs = cs_new(qcs->qcc->conn, qcs->qcc->conn->target); + cs->ctx = qcs; + stream_create_from_cs(cs, &htx_buf); + + b_del(rxbuf, b_data(rxbuf)); + b_free(&htx_buf); + + return 0; +} + +static struct buffer *mux_get_buf(struct qcs *qcs) +{ + if (!b_size(&qcs->tx.buf)) + b_alloc(&qcs->tx.buf); + + return &qcs->tx.buf; +} + +static size_t hq_interop_snd_buf(struct conn_stream *cs, struct buffer *buf, + size_t count, int flags) +{ + struct qcs *qcs = cs->ctx; + struct htx *htx; + enum htx_blk_type btype; + struct htx_blk *blk; + int32_t idx; + uint32_t bsize, fsize; + struct buffer *res, outbuf; + size_t total = 0; + + htx = htx_from_buf(buf); + res = mux_get_buf(qcs); + outbuf = b_make(b_tail(res), b_contig_space(res), 0, 0); + + while (count && !htx_is_empty(htx) && !(qcs->flags & QC_SF_BLK_MROOM)) { + /* Not implemented : QUIC on backend side */ + idx = htx_get_head(htx); + blk = htx_get_blk(htx, idx); + btype = htx_get_blk_type(blk); + fsize = bsize = htx_get_blksz(blk); + + BUG_ON(btype == HTX_BLK_REQ_SL); + + switch (btype) { + case HTX_BLK_DATA: + if (fsize > count) + fsize = count; + b_putblk(&outbuf, htx_get_blk_ptr(htx, blk), fsize); + total += fsize; + count -= fsize; + + if (fsize == bsize) + htx_remove_blk(htx, blk); + else + htx_cut_data_blk(htx, blk, fsize); + break; + + /* only body is transfered on HTTP/0.9 */ + case HTX_BLK_RES_SL: + case HTX_BLK_TLR: + case HTX_BLK_EOT: + default: + htx_remove_blk(htx, blk); + total += bsize; + count -= bsize; + break; + } + } + + if ((htx->flags & HTX_FL_EOM) && htx_is_empty(htx)) + qcs->flags |= QC_SF_FIN_STREAM; + + b_add(res, b_data(&outbuf)); + + if (total) { + if (!(qcs->qcc->wait_event.events & SUB_RETRY_SEND)) + tasklet_wakeup(qcs->qcc->wait_event.tasklet); + } + + return total; +} + +const struct qcc_app_ops hq_interop_ops = { + .decode_qcs = hq_interop_decode_qcs, + .snd_buf = hq_interop_snd_buf, +}; diff --git a/src/xprt_quic.c b/src/xprt_quic.c index d77540a7b..35afeaaf0 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1670,6 +1671,9 @@ static inline int qc_provide_cdata(struct quic_enc_level *el, if (!qc->qcc->app_ops->init(qc->qcc)) goto err; } + else if (alpn_len >= 10 && memcmp(alpn, "hq-interop", 10) == 0) { + qc->qcc->app_ops = &hq_interop_ops; + } else { /* TODO RFC9001 8.1. Protocol Negotiation * must return no_application_protocol TLS alert