diff --git a/include/haproxy/h2.h b/include/haproxy/h2.h index 45c3d8f10..6bf9df9cc 100644 --- a/include/haproxy/h2.h +++ b/include/haproxy/h2.h @@ -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); /* diff --git a/src/h2.c b/src/h2.c index a4279d324..9154bd50d 100644 --- 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. + * + * 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; diff --git a/src/mux_h2.c b/src/mux_h2.c index 3565b7a19..a9218308b 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -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 */ 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);