From b6503f70e27d8b153d182f9440f0da0a56de4c44 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Tue, 5 May 2026 18:13:21 +0200 Subject: [PATCH] BUG/MEDIUM: haterm: Properly handle client timeout No client timeout was set with haterm. It could be an issue with unresponsive clients. So the I/O timeout of the SC is initialized to the frontend client timeout when the hstream is created. Then a read activity is reported when data are received. This read activity is used to set an expiration date on the hstream task and test it when the hstream is woken up with TASK_WOKEN_TIMER reason. When a client timeout is detected, the hstream try to send a 408 and report an error. --- src/haterm.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/haterm.c b/src/haterm.c index 386730f88..0c9ffe17e 100644 --- a/src/haterm.c +++ b/src/haterm.c @@ -281,6 +281,7 @@ static int hstream_htx_buf_rcv(struct connection *conn, struct hstream *hs) end_recv: if (cur_read) { hs->req_body = ((hs->req_body < cur_read) ? 0 : hs->req_body - cur_read); + sc_ep_report_read_activity(hs->sc); } if (((conn->flags & CO_FL_ERROR) || sc_ep_test(hs->sc, SE_FL_ERROR))) { @@ -879,6 +880,23 @@ static inline int hstream_sl_hdrs_htx_buf_snd(struct hstream *hs, return ret; } +/* Send an error to the client, if possible */ +static inline void hstream_send_error(struct hstream *hs, struct connection *conn, struct buffer *errmsg) +{ + /* Do nothing is the response headers were already sent */ + if (hs->flags & HS_ST_HTTP_RESP_SL_SENT) + return; + + if (!hstream_get_buf(hs, &hs->res)) { + TRACE_ERROR("could not allocate response buffer", HS_EV_HSTRM_RESP, hs); + return; + } + + b_set_data(&hs->res, b_data(errmsg)); + memcpy(b_orig(&hs->res), b_head(errmsg), b_data(errmsg)); + hstream_htx_buf_snd(conn, hs); +} + /* Must be called before sending to determine if the body request must be * drained asap before sending. Return 1 if this is the case, 0 if not. * This is the case by default before sending the response except if @@ -929,6 +947,16 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int TRACE_STATE("waiting before responding", HS_EV_HSTRM_IO_CB, hs); goto leave; } + if (state & TASK_WOKEN_TIMER) { + int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY); + + if (tick_is_expired(exp, now_ms)) { + TRACE_ERROR("connection timed out", HS_EV_PROCESS_HSTRM, hs); + hs->flags |= HS_ST_CONN_ERROR; + hstream_send_error(hs, conn, &http_err_chunks[HTTP_ERR_408]); + goto out; + } + } if (!(hs->flags & HS_ST_HTTP_GOT_HDRS)) { struct htx *htx = htx_from_buf(&hs->req); @@ -1076,6 +1104,14 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int task_destroy(t); t = NULL; } + else { + t->expire = (tick_is_expired(t->expire, now_ms) ? TICK_ETERNITY : t->expire); + if (!(hs->flags & HS_ST_HTTP_EOM_RCVD)) { + int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY); + + t->expire = tick_first(t->expire, exp); + } + } leave: TRACE_LEAVE(HS_EV_PROCESS_HSTRM, hs); @@ -1109,6 +1145,7 @@ void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input) hs->obj_type = OBJ_TYPE_HATERM; hs->sess = sess; hs->sc = sc; + hs->sc->ioto = sess->fe->timeout.client; hs->task = t; hs->req = BUF_NULL; hs->res = BUF_NULL;