BUG/MEDIUM: stream: Properly handle destructive client connection upgrades

When the protocol is changed for a client connection at the stream level
(from TCP to H1/H2), there are two cases. The stream may be reused or
not. The first case, when the stream is reused is working. The second one is
buggy since the conn-stream refactoring and leads to a crash.

In this case, the new mux don't reuse the stream. It must be silently
aborted. However, its front stream connector is still referencing the
connection. So it must be detached. But it must be performed in two stages,
to be sure to not loose the context for the upgrade and to be able to
rollback on error. So now, before the upgrade, we prepare to detach the
stconn and it is finally detached if the upgrade succeeds. There is a trick
here. Because we pretend the stconn is detached but its state is preserved.

This patch must be backported to 2.6.
This commit is contained in:
Christopher Faulet 2022-06-16 16:24:16 +02:00
parent 9b3aa63df7
commit b68f77d626
3 changed files with 42 additions and 1 deletions

View File

@ -51,6 +51,10 @@ int sc_reset_endp(struct stconn *sc);
struct appctx *sc_applet_create(struct stconn *sc, struct applet *app);
void sc_conn_prepare_endp_upgrade(struct stconn *sc);
void sc_conn_abort_endp_upgrade(struct stconn *sc);
void sc_conn_commit_endp_upgrade(struct stconn *sc);
/* The se_fl_*() set of functions manipulate the stream endpoint flags from
* the stream endpoint itself. The sc_ep_*() set of functions manipulate the
* stream endpoint flags from the the stream connector (ex. stconn).

View File

@ -1943,3 +1943,35 @@ static int sc_applet_process(struct stconn *sc)
appctx_wakeup(__sc_appctx(sc));
return 0;
}
/* Prepares an endpoint upgrade. We don't now at this stage if the upgrade will
* succeed or not and if the stconn will be reused by the new endpoint. Thus,
* for now, only pretend the stconn is detached.
*/
void sc_conn_prepare_endp_upgrade(struct stconn *sc)
{
BUG_ON(!sc_conn(sc) || !sc->app);
sc_ep_clr(sc, SE_FL_T_MUX);
sc_ep_set(sc, SE_FL_DETACHED);
}
/* Endpoint upgrade failed. Retore the stconn state. */
void sc_conn_abort_endp_upgrade(struct stconn *sc)
{
sc_ep_set(sc, SE_FL_T_MUX);
sc_ep_clr(sc, SE_FL_DETACHED);
}
/* Commit the endpoint upgrade. If stconn is attached, it means the new endpoint
* use it. So we do nothing. Otherwise, the stconn will be destroy with the
* overlying stream. So, it means we must commit the detach.
*/
void sc_conn_commit_endp_upgrade(struct stconn *sc)
{
if (!sc_ep_test(sc, SE_FL_DETACHED))
return;
sc_detach_endp(&sc);
/* Because it was already set as detached, the sedesc must be preserved */
BUG_ON(!sc->sedesc);
}

View File

@ -1485,10 +1485,15 @@ int stream_set_http_mode(struct stream *s, const struct mux_proto_list *mux_prot
if (conn->mux->flags & MX_FL_NO_UPG)
return 0;
sc_conn_prepare_endp_upgrade(sc);
if (conn_upgrade_mux_fe(conn, sc, &s->req.buf,
(mux_proto ? mux_proto->token : ist("")),
PROTO_MODE_HTTP) == -1)
PROTO_MODE_HTTP) == -1) {
sc_conn_abort_endp_upgrade(sc);
return 0;
}
sc_conn_commit_endp_upgrade(sc);
s->req.flags &= ~(CF_READ_PARTIAL|CF_AUTO_CONNECT);
s->req.total = 0;