]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: htx: Fix the process of HTTP CONNECT with h2 connections
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Mar 2019 10:41:39 +0000 (11:41 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 12 Apr 2019 20:06:53 +0000 (22:06 +0200)
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.

src/mux_h1.c
src/proto_htx.c

index 9fb570d026ee56f022bc6e7d3df21ce858e8bb88..91d94eda28a562e98435530620e3672f0e95c6a6 100644 (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:
index 33879438a819100d54b7168f5303f5a2907f651c..3837666b0f4004ae5632278b18b8002a4fcfb3bd 100644 (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