]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h2: parse Extended CONNECT reponse to htx
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 11 Dec 2020 16:53:05 +0000 (17:53 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Jan 2021 15:37:14 +0000 (16:37 +0100)
Support for the rfc 8441 Bootstraping WebSockets with HTTP/2

Convert a 200 status reply from an Extended CONNECT request into a htx
representation. The htx message is set to 101 status code to be fully
compatible with the equivalent HTTP/1.1 Upgrade mechanism.

This conversion is only done if the stream flags H2_SF_EXT_CONNECT_SENT
has been set. This is true if an Extended CONNECT request has already
been seen on the stream.

Besides the 101 status, the additional headers Connection/Upgrade are
added to the htx message. The protocol is set from the value stored in
h2s. Typically it will be extracted from the client request. This is
only used if the client is using h1 as only the HTTP/1.1 101 Response
contains the Upgrade header.

include/haproxy/h2.h
src/h2.c
src/mux_h2.c

index 45c3d8f10386bf07d4954a95e2d71bb1cf4d9d4f..6bf9df9ccee0d32db10016579764b83337b1a54f 100644 (file)
@@ -179,6 +179,7 @@ enum h2_err {
 #define H2_MSGF_RSP_1XX        0x0010    // a 1xx ( != 101) HEADERS frame was received
 #define H2_MSGF_BODYLESS_RSP   0x0020    // response message is known to have no body
                                          // (response to HEAD request or 204/304 response)
+#define H2_MSGF_EXT_CONNECT    0x0040    // Extented CONNECT method from rfc 8441
 
 #define H2_MAX_STREAM_ID       ((1U << 31) - 1)
 #define H2_MAX_FRAME_LEN       ((1U << 24) - 1)
@@ -204,7 +205,7 @@ extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES];
 
 int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len);
 int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
-int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
+int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, char *upgrade_protocol);
 int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx);
 
 /*
index a4279d32447464176d29b0d669e5c419589921b1..9154bd50d3b59bb4a5b2d4ada89d8e6a43d3f01e 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
@@ -529,7 +529,7 @@ static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr,
 {
        unsigned int status, flags = HTX_SL_F_NONE;
        struct htx_sl *sl;
-       unsigned char h, t, u;
+       struct ist stat;
 
        /* only :status is allowed as a pseudo header */
        if (!(fields & H2_PHDR_FND_STAT))
@@ -538,12 +538,25 @@ static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr,
        if (phdr[H2_PHDR_IDX_STAT].len != 3)
                goto fail;
 
-       h = phdr[H2_PHDR_IDX_STAT].ptr[0] - '0';
-       t = phdr[H2_PHDR_IDX_STAT].ptr[1] - '0';
-       u = phdr[H2_PHDR_IDX_STAT].ptr[2] - '0';
-       if (h > 9 || t > 9 || u > 9)
-               goto fail;
-       status = h * 100 + t * 10 + u;
+       /* if Extended CONNECT is used, convert status code from 200 to htx 101
+        * following rfc 8441 */
+       if (unlikely(*msgf & H2_MSGF_EXT_CONNECT) &&
+           isteq(phdr[H2_PHDR_IDX_STAT], ist("200"))) {
+               stat = ist("101");
+               status = 101;
+       }
+       else {
+               unsigned char h, t, u;
+
+               stat = phdr[H2_PHDR_IDX_STAT];
+
+               h = stat.ptr[0] - '0';
+               t = stat.ptr[1] - '0';
+               u = stat.ptr[2] - '0';
+               if (h > 9 || t > 9 || u > 9)
+                       goto fail;
+               status = h * 100 + t * 10 + u;
+       }
 
        /* 101 responses are not supported in H2, so return a error.
         * On 1xx responses there is no ES on the HEADERS frame but there is no
@@ -551,14 +564,20 @@ static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr,
         * notify the decoder another HEADERS frame is expected.
         * 204/304 resposne have no body by definition. So remove the flag
         * H2_MSGF_BODY and set H2_MSGF_BODYLESS_RSP.
+        *
+        * Note however that there is a special condition for Extended CONNECT.
+        * In this case, we explicitly convert it to HTX 101 to mimic
+        * Get+Upgrade HTTP/1.1 mechanism
         */
-       if (status == 101)
-               goto fail;
+       if (status == 101) {
+               if (!(*msgf & H2_MSGF_EXT_CONNECT))
+                       goto fail;
+       }
        else if (status < 200) {
                *msgf |= H2_MSGF_RSP_1XX;
                *msgf &= ~H2_MSGF_BODY;
        }
-       else if (sl->info.res.status == 204 || sl->info.res.status == 304) {
+       else if (status == 204 || status == 304) {
                *msgf &= ~H2_MSGF_BODY;
                *msgf |= H2_MSGF_BODYLESS_RSP;
        }
@@ -567,7 +586,7 @@ static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr,
        flags |= HTX_SL_F_VER_11;    // V2 in fact
        flags |= HTX_SL_F_XFER_LEN;  // xfer len always known with H2
 
-       sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/2.0"), phdr[H2_PHDR_IDX_STAT], ist(""));
+       sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/2.0"), stat, ist(""));
        if (!sl)
                goto fail;
        sl->info.res.status = status;
@@ -593,8 +612,11 @@ static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr,
  *   - n.name ignored, n.len == 0 : end of list
  *   - in all cases except the end of list, v.name and v.len must designate a
  *     valid value.
+ *
+ * <upgrade_protocol> is only used if the htx status code is 101 indicating a
+ * response to an upgrade or h2-equivalent request.
  */
-int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len)
+int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, char *upgrade_protocol)
 {
        struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
        uint32_t fields; /* bit mask of H2_PHDR_FND_* */
@@ -698,7 +720,16 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m
                        goto fail;
        }
 
-       if ((*msgf & H2_MSGF_BODY_TUNNEL) && sl->info.res.status >= 200 && sl->info.res.status < 300)
+       if (sl->info.res.status == 101 && upgrade_protocol) {
+               if (!htx_add_header(htx, ist("connection"), ist("upgrade")))
+                       goto fail;
+               if (!htx_add_header(htx, ist("upgrade"), ist(upgrade_protocol)))
+                       goto fail;
+               sl_flags |= HTX_SL_F_CONN_UPG;
+       }
+
+       if ((*msgf & H2_MSGF_BODY_TUNNEL) &&
+           ((sl->info.res.status >= 200 && sl->info.res.status < 300) || sl->info.res.status == 101))
                *msgf &= ~(H2_MSGF_BODY|H2_MSGF_BODY_CL);
        else
                *msgf &= ~H2_MSGF_BODY_TUNNEL;
index 3565b7a19a5a4664f79ba94affa5e565999cc6da..a9218308b280bf84cf0fc259206747be44911278 100644 (file)
@@ -217,6 +217,8 @@ struct h2s {
        struct list list; /* To be used when adding in h2c->send_list or h2c->fctl_lsit */
        struct tasklet *shut_tl;  /* deferred shutdown tasklet, to retry to send an RST after we failed to,
                                   * in case there's no other subscription to do it */
+
+       char upgrade_protocol[16]; /* rfc 8441: requested protocol on Extended CONNECT */
 };
 
 /* descriptor for an h2 frame header */
@@ -555,7 +557,7 @@ static int h2_process(struct h2c *h2c);
 /* h2_io_cb is exported to see it resolved in "show fd" */
 struct task *h2_io_cb(struct task *t, void *ctx, unsigned short state);
 static inline struct h2s *h2c_st_by_id(struct h2c *h2c, int id);
-static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len);
+static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len, char *upgrade_protocol);
 static int h2_frt_transfer_data(struct h2s *h2s);
 static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short state);
 static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, struct session *sess);
@@ -1443,6 +1445,7 @@ static struct h2s *h2s_new(struct h2c *h2c, int id)
        h2s->status    = 0;
        h2s->body_len  = 0;
        h2s->rxbuf     = BUF_NULL;
+       memset(h2s->upgrade_protocol, 0, sizeof(h2s->upgrade_protocol));
 
        h2s->by_id.key = h2s->id = id;
        if (id > 0)
@@ -2617,7 +2620,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
        if (h2s->st != H2_SS_IDLE) {
                /* The stream exists/existed, this must be a trailers frame */
                if (h2s->st != H2_SS_CLOSED) {
-                       error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len);
+                       error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len, NULL);
                        /* unrecoverable error ? */
                        if (h2c->st0 >= H2_CS_ERROR)
                                goto out;
@@ -2638,7 +2641,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
                /* the connection was already killed by an RST, let's consume
                 * the data and send another RST.
                 */
-               error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
+               error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len, NULL);
                h2s = (struct h2s*)h2_error_stream;
                goto send_rst;
        }
@@ -2653,7 +2656,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
        else if (h2c->flags & H2_CF_DEM_TOOMANY)
                goto out; // IDLE but too many cs still present
 
-       error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
+       error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len, NULL);
 
        /* unrecoverable error ? */
        if (h2c->st0 >= H2_CS_ERROR)
@@ -2748,13 +2751,13 @@ static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s)
                goto fail; // incomplete frame
 
        if (h2s->st != H2_SS_CLOSED) {
-               error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &h2s->body_len);
+               error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &h2s->body_len, h2s->upgrade_protocol);
        }
        else {
                /* the connection was already killed by an RST, let's consume
                 * the data and send another RST.
                 */
-               error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
+               error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len, NULL);
                h2s = (struct h2s*)h2_error_stream;
                h2c->st0 = H2_CS_FRAME_E;
                goto send_rst;
@@ -4522,7 +4525,7 @@ static void h2_shutw(struct conn_stream *cs, enum cs_shw_mode mode)
  * decoding, in order to detect if we're dealing with a headers or a trailers
  * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen).
  */
-static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len)
+static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len, char *upgrade_protocol)
 {
        const uint8_t *hdrs = (uint8_t *)b_head(&h2c->dbuf);
        struct buffer *tmp = get_trash_chunk();
@@ -4679,13 +4682,16 @@ next_frame:
        /* OK now we have our header list in <list> */
        msgf = (h2c->dff & H2_F_HEADERS_END_STREAM) ? 0 : H2_MSGF_BODY;
        msgf |= (*flags & H2_SF_BODY_TUNNEL) ? H2_MSGF_BODY_TUNNEL: 0;
+       /* If an Extended CONNECT has been sent on this stream, set message flag
+        * to convert 200 response to 101 htx reponse */
+       msgf |= (*flags & H2_SF_EXT_CONNECT_SENT) ? H2_MSGF_EXT_CONNECT: 0;
 
        if (*flags & H2_SF_HEADERS_RCVD)
                goto trailers;
 
        /* This is the first HEADERS frame so it's a headers block */
        if (h2c->flags & H2_CF_IS_BACK)
-               outlen = h2_make_htx_response(list, htx, &msgf, body_len);
+               outlen = h2_make_htx_response(list, htx, &msgf, body_len, upgrade_protocol);
        else
                outlen = h2_make_htx_request(list, htx, &msgf, body_len);