mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 15:47:01 +02:00
MEDIUM: h2: parse Extended CONNECT reponse to htx
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.
This commit is contained in:
parent
5fb48ea7a4
commit
7416274914
@ -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);
|
||||
|
||||
/*
|
||||
|
57
src/h2.c
57
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;
|
||||
|
22
src/mux_h2.c
22
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 @@ static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f
|
||||
/* 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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user