]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: mux-h1: Properly handle tunnel establishments and aborts
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 22 Jan 2021 14:12:30 +0000 (15:12 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Jan 2021 15:37:14 +0000 (16:37 +0100)
In the same way than the H2 mux, we now bloc data sending on the server side
if a tunnel is not fully established. In addition, if some data are still
pending for a aborted tunnel, an error is triggered and the server
connection is closed.

To do so, we rely on the H1C_F_WAIT_INPUT flag to bloc the output
processing. This patch contributes to fix the tunnel mode between the H1 and
the H2 muxes.

src/mux_h1.c

index aad155a498358b4cdcbd658812aa05e1c892c492..b73ceb1fc3e211c34c114570f05fc0fede265424 100644 (file)
@@ -1318,16 +1318,30 @@ static void h1_set_res_tunnel_mode(struct h1s *h1s)
                        h1s->h1c->flags |= H1C_F_WAIT_OUTPUT;
                        TRACE_STATE("Disable read on h1c (wait_output)", H1_EV_RX_DATA|H1_EV_H1C_BLK, h1s->h1c->conn, h1s);
                }
-               else if (h1s->status == 101 && h1s->req.state == H1_MSG_DONE) {
+               else  {
                        h1s->req.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
                        h1s->req.state = H1_MSG_TUNNEL;
                        TRACE_STATE("switch H1 request in tunnel mode", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1s->h1c->conn, h1s);
+
+                       if (h1s->h1c->flags & H1C_F_WAIT_INPUT) {
+                               h1s->h1c->flags &= ~H1C_F_WAIT_INPUT;
+                               h1_wake_stream_for_send(h1s);
+                               if (b_data(&h1s->h1c->obuf))
+                                       tasklet_wakeup(h1s->h1c->wait_event.tasklet);
+                               TRACE_STATE("Re-enable send on h1c", H1_EV_TX_DATA|H1_EV_H1C_BLK|H1_EV_H1C_WAKE, h1s->h1c->conn, h1s);
+                       }
                }
        }
-       else if (h1s->h1c->flags & H1C_F_WAIT_OUTPUT) {
-               h1s->h1c->flags &= ~H1C_F_WAIT_OUTPUT;
-               tasklet_wakeup(h1s->h1c->wait_event.tasklet);
-               TRACE_STATE("Re-enable read on h1c", H1_EV_RX_DATA|H1_EV_H1C_BLK|H1_EV_H1C_WAKE, h1s->h1c->conn, h1s);
+       else {
+               h1s->req.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
+               h1s->req.state = H1_MSG_TUNNEL;
+               TRACE_STATE("switch H1 request in tunnel mode", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1s->h1c->conn, h1s);
+
+               if (h1s->h1c->flags & H1C_F_WAIT_OUTPUT) {
+                       h1s->h1c->flags &= ~H1C_F_WAIT_OUTPUT;
+                       tasklet_wakeup(h1s->h1c->wait_event.tasklet);
+                       TRACE_STATE("Re-enable read on h1c", H1_EV_RX_DATA|H1_EV_H1C_BLK|H1_EV_H1C_WAKE, h1s->h1c->conn, h1s);
+               }
        }
 }
 
@@ -1713,6 +1727,9 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
        if (h1s->flags & H1S_F_PROCESSING_ERROR)
                goto end;
 
+       if (h1c->flags & H1C_F_WAIT_INPUT)
+               goto end;
+
        if (!h1_get_buf(h1c, &h1c->obuf)) {
                h1c->flags |= H1C_F_OUT_ALLOC;
                TRACE_STATE("waiting for h1c obuf allocation", H1_EV_TX_DATA|H1_EV_H1S_BLK, h1c->conn, h1s);
@@ -1741,7 +1758,8 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
         * the HTX blocks.
         */
        if (!b_data(&h1c->obuf)) {
-               if (htx_nbblks(chn_htx) == 1 &&
+               if ((h1m->state == H1_MSG_DATA || h1m->state == H1_MSG_TUNNEL) &&
+                   htx_nbblks(chn_htx) == 1 &&
                    htx_get_blk_type(blk) == HTX_BLK_DATA &&
                    htx_get_blk_value(chn_htx, blk).len == count) {
                        void *old_area = h1c->obuf.area;
@@ -1782,7 +1800,7 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
 
        tmp.data = 0;
        tmp.size = b_room(&h1c->obuf);
-       while (count && !(h1s->flags & H1S_F_PROCESSING_ERROR) && blk) {
+       while (count && !(h1s->flags & H1S_F_PROCESSING_ERROR) && !(h1c->flags & H1C_F_WAIT_INPUT) && blk) {
                struct htx_sl *sl;
                struct ist n, v;
                enum htx_blk_type type = htx_get_blk_type(blk);
@@ -1948,16 +1966,11 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                            H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
 
                                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);
+                                       goto done;
                                }
                                else if ((h1m->flags & H1_MF_RESP) &&
                                         ((h1s->meth == HTTP_METH_CONNECT && h1s->status >= 200 && h1s->status < 300) || h1s->status == 101)) {
-                                       /* a successful 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);
-                                       TRACE_STATE("switch H1 response in tunnel mode", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
+                                       goto done;
                                }
                                else if ((h1m->flags & H1_MF_RESP) &&
                                         h1s->status < 200 && (h1s->status == 100 || h1s->status >= 102)) {
@@ -2066,9 +2079,17 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                        goto error;
                          done:
                                h1m->state = H1_MSG_DONE;
-                               if (!(h1m->flags & H1_MF_RESP) && h1s->status == 101) {
-                                       h1_set_req_tunnel_mode(h1s);
-                                       TRACE_STATE("switch H1 request in tunnel mode", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
+                               if (!(h1m->flags & H1_MF_RESP) && h1s->meth == HTTP_METH_CONNECT) {
+                                       h1c->flags |= H1C_F_WAIT_INPUT;
+                                       TRACE_STATE("Disable send on h1c (wait_input)", H1_EV_TX_DATA|H1_EV_H1C_BLK, h1c->conn, h1s);
+                               }
+                               else if ((h1m->flags & H1_MF_RESP) &&
+                                        ((h1s->meth == HTTP_METH_CONNECT && h1s->status >= 200 && h1s->status < 300) || h1s->status == 101)) {
+                                       /* a successful 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);
+                                       TRACE_STATE("switch H1 response in tunnel mode", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
                                }
                                else if (h1s->h1c->flags & H1C_F_WAIT_OUTPUT) {
                                        h1s->h1c->flags &= ~H1C_F_WAIT_OUTPUT;
@@ -2086,6 +2107,7 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                /* Unexpected error during output processing */
                                chn_htx->flags |= HTX_FL_PROCESSING_ERROR;
                                h1s->flags |= H1S_F_PROCESSING_ERROR;
+                               h1c->flags |= H1C_F_ST_ERROR;
                                TRACE_STATE("processing error, set error on h1c/h1s", H1_EV_H1C_ERR|H1_EV_H1S_ERR, h1c->conn, h1s);
                                TRACE_DEVEL("unexpected error", H1_EV_TX_DATA|H1_EV_STRM_ERR, h1c->conn, h1s);
                                break;
@@ -2113,18 +2135,24 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
 
        htx_to_buf(chn_htx, buf);
   out:
-       /* Both the request and the response reached the DONE state. So set EOI
-        * flag on the conn-stream. Most of time, the flag will already be set,
-        * except for protocol upgrades.
-        */
-       if (h1s->cs && h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE)
-                       h1s->cs->flags |= CS_FL_EOI;
-
        if (!buf_room_for_htx_data(&h1c->obuf)) {
                TRACE_STATE("h1c obuf full", H1_EV_TX_DATA|H1_EV_H1S_BLK, h1c->conn, h1s);
                h1c->flags |= H1C_F_OUT_FULL;
        }
   end:
+       /* Both the request and the response reached the DONE state. So set EOI
+        * flag on the conn-stream. Most of time, the flag will already be set,
+        * except for protocol upgrades. Report an error if data remains blocked
+        * in the output buffer.
+        */
+       if (h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) {
+               if (!htx_is_empty(chn_htx)) {
+                       h1c->flags |= H1C_F_ST_ERROR;
+                       TRACE_STATE("txn done but data waiting to be sent, set error on h1c", H1_EV_H1C_ERR, h1c->conn, h1s);
+               }
+               h1s->cs->flags |= CS_FL_EOI;
+       }
+
        TRACE_LEAVE(H1_EV_TX_DATA, h1c->conn, h1s, chn_htx, (size_t[]){total});
        return total;
 
@@ -3180,6 +3208,12 @@ static size_t h1_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
                return 0;
        }
 
+       if (h1c->flags & H1C_F_ST_ERROR) {
+               cs->flags |= CS_FL_ERROR;
+               TRACE_DEVEL("H1 connection is in error, leaving in error", H1_EV_STRM_SEND|H1_EV_H1C_ERR|H1_EV_H1S_ERR|H1_EV_STRM_ERR, h1c->conn, h1s);
+               return 0;
+       }
+
        /* Inherit some flags from the upper layer */
        h1c->flags &= ~(H1C_F_CO_MSG_MORE|H1C_F_CO_STREAMER);
        if (flags & CO_SFL_MSG_MORE)
@@ -3205,6 +3239,12 @@ static size_t h1_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
                if ((h1c->wait_event.events & SUB_RETRY_SEND) || !h1_send(h1c))
                        break;
        }
+
+       if (h1c->flags & H1C_F_ST_ERROR) {
+               TRACE_DEVEL("reporting error to the app-layer stream", H1_EV_STRM_SEND|H1_EV_H1S_ERR|H1_EV_STRM_ERR, h1c->conn, h1s);
+               cs->flags |= CS_FL_ERROR;
+       }
+
        h1_refresh_timeout(h1c);
        TRACE_LEAVE(H1_EV_STRM_SEND, h1c->conn, h1s, 0, (size_t[]){total});
        return total;