MEDIUM: mux-h1: Properly handle tunnel establishments and aborts
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.
diff --git a/src/mux_h1.c b/src/mux_h1.c
index aad155a..b73ceb1 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -1318,16 +1318,30 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
/* 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 @@
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 @@
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 @@
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;