BUG/MEDIUM: htx: Fix the process of HTTP CONNECT with h2 connections

In HTX, the HTTP tunneling does not work if h1 and h2 are mixed (an h1 client
sending requests to an h2 server or this opposite) because the h1 multiplexer
always adds an EOM before switching it to tunnel mode. The h2 multiplexer
interprets it as an end of stream, closing the stream as for any other
transaction.

To make it works again, we need to swith to the tunnel mode without emitting any
EOM blocks. Because of that, HTX analyzers have been updated to switch the
transaction to tunnel mode before end of the message (because there is no end of
message...).

To be consistent, the protocol switching is also handled the same way even
though the 101 responses are not supported in h2.

This patch must be backported to 1.9.
This commit is contained in:
Christopher Faulet 2019-03-28 11:41:39 +01:00
parent 03b9d8ba4a
commit c62c2b9d92
2 changed files with 81 additions and 25 deletions

View File

@ -910,6 +910,41 @@ static void h1_emit_chunk_crlf(struct buffer *buf)
b_add(buf, 2);
}
/*
* Switch the request to tunnel mode. This function must only be called for
* CONNECT requests. On the client side, the mux is mark as busy on input,
* waiting the response.
*/
static void h1_set_req_tunnel_mode(struct h1s *h1s)
{
h1s->req.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
h1s->req.state = H1_MSG_TUNNEL;
if (!conn_is_back(h1s->h1c->conn))
h1s->h1c->flags |= H1C_F_IN_BUSY;
}
/*
* Switch the response to tunnel mode. This function must only be called on
* successfull replies to CONNECT requests or on protocol switching. On the
* server side, if the request is not finished, the mux is mark as busy on
* input. Otherwise the request is also switch to tunnel mode.
*/
static void h1_set_res_tunnel_mode(struct h1s *h1s)
{
h1s->res.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
h1s->res.state = H1_MSG_TUNNEL;
if (conn_is_back(h1s->h1c->conn) && h1s->req.state < H1_MSG_DONE)
h1s->h1c->flags |= H1C_F_IN_BUSY;
else {
h1s->req.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
h1s->req.state = H1_MSG_TUNNEL;
if (h1s->h1c->flags & H1C_F_IN_BUSY) {
h1s->h1c->flags &= ~H1C_F_IN_BUSY;
tasklet_wakeup(h1s->h1c->wait_event.task);
}
}
}
/*
* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
@ -955,10 +990,17 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *h
if (!(h1m->flags & H1_MF_RESP)) {
h1s->meth = h1sl.rq.meth;
/* Request have always a known length */
/* By default, request have always a known length */
h1m->flags |= H1_MF_XFER_LEN;
if (!(h1m->flags & H1_MF_CHNK) && !h1m->body_len)
if (h1s->meth == HTTP_METH_CONNECT) {
/* Switch CONNECT requests to tunnel mode */
h1_set_req_tunnel_mode(h1s);
}
else if (!(h1m->flags & H1_MF_CHNK) && !h1m->body_len) {
/* Switch requests with no body to done. */
h1m->state = H1_MSG_DONE;
}
if (!h1_process_req_vsn(h1s, h1m, h1sl)) {
h1m->err_pos = h1sl.rq.v.ptr - b_head(buf);
@ -969,22 +1011,32 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *h
else {
h1s->status = h1sl.st.status;
if ((h1s->meth == HTTP_METH_HEAD) ||
(h1s->status >= 100 && h1s->status < 200) ||
(h1s->status == 204) || (h1s->status == 304) ||
(h1s->meth == HTTP_METH_CONNECT && h1s->status == 200)) {
if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
h1s->status == 101) {
/* Switch successfull replies to CONNECT requests and
* protocol switching to tunnel mode. */
h1_set_res_tunnel_mode(h1s);
}
else if ((h1s->meth == HTTP_METH_HEAD) ||
(h1s->status >= 100 && h1s->status < 200) ||
(h1s->status == 204) || (h1s->status == 304)) {
/* Switch responses without body to done. */
h1m->flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
h1m->flags |= H1_MF_XFER_LEN;
h1m->curr_len = h1m->body_len = 0;
h1m->state = H1_MSG_DONE;
}
else if (h1m->flags & (H1_MF_CLEN|H1_MF_CHNK)) {
/* Responses with a known body length. Switch requests
* with no body to done. */
h1m->flags |= H1_MF_XFER_LEN;
if ((h1m->flags & H1_MF_CLEN) && !h1m->body_len)
h1m->state = H1_MSG_DONE;
}
else
else {
/* Responses with an unknown body length */
h1m->state = H1_MSG_TUNNEL;
}
if (!h1_process_res_vsn(h1s, h1m, h1sl)) {
h1m->err_pos = h1sl.st.v.ptr - b_head(buf);
@ -1292,16 +1344,6 @@ static void h1_sync_messages(struct h1c *h1c)
h1s->res.flags |= H1_MF_NO_PHDR;
h1c->flags &= ~H1C_F_IN_BUSY;
}
else if (!b_data(&h1c->obuf) &&
h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) {
if (h1s->flags & H1S_F_WANT_TUN) {
h1m_init_req(&h1s->req);
h1m_init_res(&h1s->res);
h1s->req.state = H1_MSG_TUNNEL;
h1s->res.state = H1_MSG_TUNNEL;
h1c->flags &= ~H1C_F_IN_BUSY;
}
}
}
/*
@ -1588,7 +1630,8 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
}
}
if (((h1m->flags & (H1_MF_VER_11|H1_MF_RESP|H1_MF_CLEN|H1_MF_CHNK|H1_MF_XFER_LEN)) ==
if ((h1s->meth != HTTP_METH_CONNECT &&
(h1m->flags & (H1_MF_VER_11|H1_MF_RESP|H1_MF_CLEN|H1_MF_CHNK|H1_MF_XFER_LEN)) ==
(H1_MF_VER_11|H1_MF_XFER_LEN)) ||
(h1s->status >= 200 && h1s->status != 204 && h1s->status != 304 &&
h1s->meth != HTTP_METH_HEAD && !(h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) &&
@ -1604,7 +1647,17 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
if (!chunk_memcat(tmp, "\r\n", 2))
goto copy;
h1m->state = H1_MSG_DATA;
if (!(h1m->flags & H1_MF_RESP) && h1s->meth == HTTP_METH_CONNECT) {
/* a CONNECT request is sent to the server. Switch it to tunnel mode. */
h1_set_req_tunnel_mode(h1s);
}
else if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) || h1s->status == 101) {
/* a successfull reply to a CONNECT or a protocol switching is sent
* to the client . Switch the response to tunnel mode. */
h1_set_res_tunnel_mode(h1s);
}
else
h1m->state = H1_MSG_DATA;
break;
case HTX_BLK_DATA:

View File

@ -1236,6 +1236,11 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit)
channel_htx_forward_forever(req, htx);
}
if (txn->meth == HTTP_METH_CONNECT) {
msg->msg_state = HTTP_MSG_TUNNEL;
goto done;
}
/* Check if the end-of-message is reached and if so, switch the message
* in HTTP_MSG_DONE state.
*/
@ -2159,12 +2164,10 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit)
channel_htx_forward_forever(res, htx);
}
if (!(msg->flags & HTTP_MSGF_XFER_LEN)) {
/* The server still sending data that should be filtered */
if (res->flags & CF_SHUTR || !HAS_RSP_DATA_FILTERS(s)) {
msg->msg_state = HTTP_MSG_TUNNEL;
goto done;
}
if ((txn->meth == HTTP_METH_CONNECT && txn->status == 200) || txn->status == 101 ||
(!(msg->flags & HTTP_MSGF_XFER_LEN) && (res->flags & CF_SHUTR || !HAS_RSP_DATA_FILTERS(s)))) {
msg->msg_state = HTTP_MSG_TUNNEL;
goto done;
}
/* Check if the end-of-message is reached and if so, switch the message