MEDIUM: Add connect/queue/tarpit timeouts to set-timeout

Add the ability to set connect, queue and tarpit timeouts from the
set-timeout action. This is especially useful when using set-dst to
dynamically connect to servers.

This patch also adds the relevant fe_/be_/cur_ sample fetches for these
timeouts.
This commit is contained in:
Nenad Merdanovic 2026-02-18 15:56:36 +01:00 committed by Christopher Faulet
parent c26c721312
commit 5a079d1811
8 changed files with 225 additions and 16 deletions

View File

@ -16357,20 +16357,24 @@ set-status <status> [reason <str>]
http-response set-status 503 reason "Slow Down".
set-timeout { client | server | tunnel } { <timeout> | <expr> }
set-timeout { client | connect | queue | server | tarpit | tunnel } { <timeout> | <expr> }
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
- | - | - | - | - | X | X | -
This action overrides the specified "client", "server" or "tunnel" timeout
for the current stream only. The timeout can be specified in milliseconds or
with any other unit if the number is suffixed by the unit as explained at the
top of this document. It is also possible to write an expression which must
return a number interpreted as a timeout in milliseconds.
This action overrides the specified "client", "connect", "queue", "server",
"tarpit" or "tunnel" timeout for the current stream only. Changing one timeout
does not influence any other timeouts, even if they are inherited from each
other during configuration parsing (see last example). The timeout can be
specified in milliseconds or with any other unit if the number is suffixed by
the unit as explained at the top of this document. It is also possible to
write an expression which must return a number interpreted as a timeout in
milliseconds.
Note that the server/tunnel timeouts are only relevant on the backend side
and thus this rule is only available for the proxies with backend
capabilities. Likewise, client timeout is only relevant for frontend side.
Also the timeout value must be non-null to obtain the expected results.
Note that the connect, queue, server and tunnel timeouts are only relevant on
the backend side and thus this rule is only available for the proxies with
backend capabilities. Likewise, client timeout is only relevant for frontend
side. Tarpit timeout is available to both sides. The timeout value must be
non-null to obtain the expected results.
Example:
http-request set-timeout tunnel 5s
@ -16380,6 +16384,18 @@ set-timeout { client | server | tunnel } { <timeout> | <expr> }
http-response set-timeout tunnel 5s
http-response set-timeout server res.hdr(X-Refresh-Seconds),mul(1000)
Example:
defaults
# This will set both tarpit and queue timeout to 5s as they are not
# defined
timeout connect 5s
timeout client 30s
timeout server 30s
listen foo
# This will only change the connect timeout to 10s without affecting
# queue or tarpit timeouts
http-request set-timeout connect 10s
set-tos <tos> (deprecated)
This is an alias for "set-fc-tos" (which should be used instead).
@ -23828,12 +23844,18 @@ bc_src_port integer
bc_srv_queue integer
be_id integer
be_name string
be_connect_timeout integer
be_queue_timeout integer
be_server_timeout integer
be_tarpit_timeout integer
be_tunnel_timeout integer
bytes_in integer
bytes_out integer
cur_connect_timeout integer
cur_client_timeout integer
cur_queue_timeout integer
cur_server_timeout integer
cur_tarpit_timeout integer
cur_tunnel_timeout integer
dst ip
dst_conn integer
@ -23867,6 +23889,7 @@ fc_src ip
fc_src_is_local boolean
fc_src_port integer
fc_unacked integer
fe_tarpit_timeout integer
fe_client_timeout integer
fe_defbe string
fe_id integer
@ -24161,6 +24184,11 @@ be_id : integer
used in a frontend and no backend was used, it returns the current
frontend's id. It can also be used in a tcp-check or an http-check ruleset.
be_connect_timeout : integer
Returns the configuration value in millisecond for the connect timeout of the
current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_connect_timeout".
be_name : string
Returns a string containing the current backend's name. It can be used in
frontends with responses to check which backend processed the request. If
@ -24168,11 +24196,21 @@ be_name : string
frontend's name. It can also be used in a tcp-check or an http-check
ruleset.
be_queue_timeout : integer
Returns the configuration value in millisecond for the queue timeout of the
current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_queue_timeout".
be_server_timeout : integer
Returns the configuration value in millisecond for the server timeout of the
current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_server_timeout".
be_tarpit_timeout : integer
Returns the configuration value in millisecond for the queue timeout of the
current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_tarpit_timeout".
be_tunnel_timeout : integer
Returns the configuration value in millisecond for the tunnel timeout of the
current backend. This timeout can be overwritten by a "set-timeout" rule. See
@ -24184,16 +24222,32 @@ bytes_in : integer
bytes_out : integer
See "res.bytes_in".
cur_connect_timeout : integer
Returns the currently applied connect timeout in millisecond for the stream.
In the default case, this will be equal to be_connect_timeout unless a
"set-timeout" rule has been applied. See also "be_connect_timeout".
cur_client_timeout : integer
Returns the currently applied client timeout in millisecond for the stream.
In the default case, this will be equal to fe_client_timeout unless a
"set-timeout" rule has been applied. See also "fe_client_timeout".
cur_queue_timeout : integer
Returns the currently applied queue timeout in millisecond for the stream.
In the default case, this will be equal to be_queue_timeout unless a
"set-timeout" rule has been applied. See also "be_queue_timeout".
cur_server_timeout : integer
Returns the currently applied server timeout in millisecond for the stream.
In the default case, this will be equal to be_server_timeout unless a
"set-timeout" rule has been applied. See also "be_server_timeout".
cur_tarpit_timeout : integer
Returns the currently applied tarpit timeout in millisecond for the stream.
In the default case, this will be equal to fe_tarpit_timeout/be_tarpit_timeout
unless a "set-timeout" rule has been applied. See also "fe_tarpit_timeout"
and "be_tarpit_timeout".
cur_tunnel_timeout : integer
Returns the currently applied tunnel timeout in millisecond for the stream.
In the default case, this will be equal to be_tunnel_timeout unless a
@ -24578,6 +24632,10 @@ fe_name : string
backends to check from which frontend it was called, or to stick all users
coming via a same frontend to the same server.
fe_tarpit_timeout : integer
Returns the configuration value in millisecond for the tarpit timeout of the
current frontend. This timeout can be overwritten by a "set-timeout" rule.
req.bytes_in : integer
This returns the number of bytes received from the client. The value
corresponds to what was received by HAProxy, including some headers and some

View File

@ -102,7 +102,10 @@ enum act_name {
/* Timeout name valid for a set-timeout rule */
enum act_timeout_name {
ACT_TIMEOUT_CONNECT,
ACT_TIMEOUT_SERVER,
ACT_TIMEOUT_QUEUE,
ACT_TIMEOUT_TARPIT,
ACT_TIMEOUT_TUNNEL,
ACT_TIMEOUT_CLIENT,
};

View File

@ -320,7 +320,10 @@ struct stream {
struct list *current_rule_list; /* this is used to store the current executed rule list. */
void *current_rule; /* this is used to store the current rule to be resumed. */
int rules_exp; /* expiration date for current rules execution */
int tunnel_timeout;
int tunnel_timeout; /* per-stream tunnel timeout, set by set-timeout action */
int connect_timeout; /* per-stream connect timeout, set by set-timeout action */
int queue_timeout; /* per-stream queue timeout, set by set-timeout action */
int tarpit_timeout; /* per-stream tarpit timeout, set by set-timeout action */
struct {
void *ptr; /* Pointer on the entity (def: NULL) */

View File

@ -188,13 +188,30 @@ int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule
const char *res;
const char *timeout_name = args[idx++];
if (strcmp(timeout_name, "server") == 0) {
if (strcmp(timeout_name, "connect") == 0) {
if (!(px->cap & PR_CAP_BE)) {
memprintf(err, "'%s' has no backend capability", px->id);
return -1;
}
rule->arg.timeout.type = ACT_TIMEOUT_CONNECT;
}
else if (strcmp(timeout_name, "server") == 0) {
if (!(px->cap & PR_CAP_BE)) {
memprintf(err, "'%s' has no backend capability", px->id);
return -1;
}
rule->arg.timeout.type = ACT_TIMEOUT_SERVER;
}
else if (strcmp(timeout_name, "queue") == 0) {
if (!(px->cap & PR_CAP_BE)) {
memprintf(err, "'%s' has no backend capability", px->id);
return -1;
}
rule->arg.timeout.type = ACT_TIMEOUT_QUEUE;
}
else if (strcmp(timeout_name, "tarpit") == 0) {
rule->arg.timeout.type = ACT_TIMEOUT_TARPIT;
}
else if (strcmp(timeout_name, "tunnel") == 0) {
if (!(px->cap & PR_CAP_BE)) {
memprintf(err, "'%s' has no backend capability", px->id);
@ -211,7 +228,7 @@ int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule
}
else {
memprintf(err,
"'set-timeout' rule supports 'server'/'tunnel'/'client' (got '%s')",
"'set-timeout' rule supports 'client'/'connect'/'queue'/'server'/'tarpit'/'tunnel' (got '%s')",
timeout_name);
return -1;
}

View File

@ -2254,7 +2254,7 @@ int connect_server(struct stream *s)
#endif
/* set connect timeout */
s->conn_exp = tick_add_ifset(now_ms, s->be->timeout.connect);
s->conn_exp = tick_add_ifset(now_ms, s->connect_timeout);
if (srv) {
int count;
@ -2377,7 +2377,7 @@ int srv_redispatch_connect(struct stream *s)
return 1;
case SRV_STATUS_QUEUED:
s->conn_exp = tick_add_ifset(now_ms, s->be->timeout.queue);
s->conn_exp = tick_add_ifset(now_ms, s->queue_timeout);
s->scb->state = SC_ST_QUE;
/* handle the unlikely event where we added to the server's
@ -3741,6 +3741,42 @@ smp_fetch_srv_uweight(const struct arg *args, struct sample *smp, const char *kw
return 1;
}
static int
smp_fetch_be_connect_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
struct proxy *px = NULL;
if (smp->strm)
px = smp->strm->be;
else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
px = __objt_check(smp->sess->origin)->proxy;
if (!px)
return 0;
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
smp->data.u.sint = TICKS_TO_MS(px->timeout.connect);
return 1;
}
static int
smp_fetch_be_queue_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
struct proxy *px = NULL;
if (smp->strm)
px = smp->strm->be;
else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
px = __objt_check(smp->sess->origin)->proxy;
if (!px)
return 0;
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
smp->data.u.sint = TICKS_TO_MS(px->timeout.queue);
return 1;
}
static int
smp_fetch_be_server_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
@ -3759,6 +3795,24 @@ smp_fetch_be_server_timeout(const struct arg *args, struct sample *smp, const ch
return 1;
}
static int
smp_fetch_be_tarpit_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
struct proxy *px = NULL;
if (smp->strm)
px = smp->strm->be;
else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
px = __objt_check(smp->sess->origin)->proxy;
if (!px)
return 0;
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
smp->data.u.sint = TICKS_TO_MS(px->timeout.tarpit);
return 1;
}
static int
smp_fetch_be_tunnel_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
@ -3859,8 +3913,11 @@ static struct sample_fetch_kw_list smp_kws = {ILH, {
{ "be_conn_free", smp_fetch_be_conn_free, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, },
{ "be_id", smp_fetch_be_id, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "be_name", smp_fetch_be_name, 0, NULL, SMP_T_STR, SMP_USE_BKEND, },
{ "be_connect_timeout",smp_fetch_be_connect_timeout,0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "be_queue_timeout", smp_fetch_be_queue_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "be_server_timeout", smp_fetch_be_server_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "be_sess_rate", smp_fetch_be_sess_rate, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, },
{ "be_tarpit_timeout", smp_fetch_be_tarpit_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "be_tunnel_timeout", smp_fetch_be_tunnel_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "connslots", smp_fetch_connslots, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, },
{ "nbsrv", smp_fetch_nbsrv, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, },

View File

@ -314,12 +314,22 @@ smp_fetch_fe_client_timeout(const struct arg *args, struct sample *smp, const ch
return 1;
}
static int
smp_fetch_fe_tarpit_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
smp->data.u.sint = TICKS_TO_MS(smp->sess->fe->timeout.tarpit);
return 1;
}
/* Note: must not be declared <const> as its list will be overwritten.
* Please take care of keeping this list alphabetically sorted.
*/
static struct sample_fetch_kw_list smp_kws = {ILH, {
{ "fe_client_timeout", smp_fetch_fe_client_timeout, 0, NULL, SMP_T_SINT, SMP_USE_FTEND, },
{ "fe_tarpit_timeout", smp_fetch_fe_tarpit_timeout, 0, NULL, SMP_T_SINT, SMP_USE_FTEND, },
{ "fe_conn", smp_fetch_fe_conn, ARG1(1,FE), NULL, SMP_T_SINT, SMP_USE_INTRN, },
{ "fe_defbe", smp_fetch_fe_defbe, 0, NULL, SMP_T_STR, SMP_USE_FTEND, },
{ "fe_id", smp_fetch_fe_id, 0, NULL, SMP_T_SINT, SMP_USE_FTEND, },

View File

@ -564,7 +564,7 @@ int http_process_req_common(struct stream *s, struct channel *req, int an_bit, s
req->analysers &= AN_REQ_FLT_END; /* remove switching rules etc... */
req->analysers |= AN_REQ_HTTP_TARPIT;
req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit);
req->analyse_exp = tick_add_ifset(now_ms, s->tarpit_timeout ? s->tarpit_timeout : s->be->timeout.tarpit);
if (!req->analyse_exp)
req->analyse_exp = tick_add(now_ms, 0);
stream_inc_http_err_ctr(s);

View File

@ -555,6 +555,9 @@ struct stream *stream_new(struct session *sess, struct stconn *sc, struct buffer
s->resolv_ctx.hostname_dn_len = 0;
s->resolv_ctx.parent = NULL;
s->connect_timeout = TICK_ETERNITY;
s->queue_timeout = TICK_ETERNITY;
s->tarpit_timeout = TICK_ETERNITY;
s->tunnel_timeout = TICK_ETERNITY;
LIST_APPEND(&th_ctx->streams, &s->list);
@ -949,10 +952,22 @@ int stream_set_timeout(struct stream *s, enum act_timeout_name name, int timeout
s->scf->ioto = timeout;
return 1;
case ACT_TIMEOUT_CONNECT:
s->connect_timeout = timeout;
return 1;
case ACT_TIMEOUT_QUEUE:
s->queue_timeout = timeout;
return 1;
case ACT_TIMEOUT_SERVER:
s->scb->ioto = timeout;
return 1;
case ACT_TIMEOUT_TARPIT:
s->tarpit_timeout = timeout;
return 1;
case ACT_TIMEOUT_TUNNEL:
s->tunnel_timeout = timeout;
return 1;
@ -1248,6 +1263,10 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an
/* Se the max connection retries for the stream. may be overwritten later */
s->max_retries = s->be->conn_retries;
/* Set the queue and connect timeouts. May be overwritten later */
s->connect_timeout = s->be->timeout.connect;
s->queue_timeout = s->be->timeout.queue;
/* we don't want to run the TCP or HTTP filters again if the backend has not changed */
if (fe == s->be) {
s->req.analysers &= ~AN_REQ_INSPECT_BE;
@ -4383,6 +4402,17 @@ static struct action_kw_list stream_http_after_res_actions = { ILH, {
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &stream_http_after_res_actions);
static int smp_fetch_cur_connect_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
if (!smp->strm)
return 0;
smp->data.u.sint = TICKS_TO_MS(smp->strm->connect_timeout);
return 1;
}
static int smp_fetch_cur_client_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
@ -4405,6 +4435,34 @@ static int smp_fetch_cur_server_timeout(const struct arg *args, struct sample *s
return 1;
}
static int smp_fetch_cur_queue_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
if (!smp->strm)
return 0;
smp->data.u.sint = TICKS_TO_MS(smp->strm->queue_timeout);
return 1;
}
static int smp_fetch_cur_tarpit_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
smp->data.type = SMP_T_SINT;
if (!smp->strm)
return 0;
if (smp->strm->tarpit_timeout)
smp->data.u.sint = TICKS_TO_MS(smp->strm->tarpit_timeout);
else if (smp->strm->be)
smp->data.u.sint = TICKS_TO_MS(smp->strm->be->timeout.tarpit);
else
smp->data.u.sint = TICKS_TO_MS(smp->sess->fe->timeout.tarpit);
return 1;
}
static int smp_fetch_cur_tunnel_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
{
smp->flags = SMP_F_VOL_TXN;
@ -4609,8 +4667,11 @@ static int smp_fetch_redispatched(const struct arg *args, struct sample *smp, co
* Please take care of keeping this list alphabetically sorted.
*/
static struct sample_fetch_kw_list smp_kws = {ILH, {
{ "cur_connect_timeout",smp_fetch_cur_connect_timeout,0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "cur_client_timeout", smp_fetch_cur_client_timeout, 0, NULL, SMP_T_SINT, SMP_USE_FTEND, },
{ "cur_server_timeout", smp_fetch_cur_server_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "cur_queue_timeout", smp_fetch_cur_queue_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "cur_tarpit_timeout", smp_fetch_cur_tarpit_timeout, 0, NULL, SMP_T_SINT, SMP_USE_FTEND | SMP_USE_BKEND, },
{ "cur_tunnel_timeout", smp_fetch_cur_tunnel_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
{ "last_entity", smp_fetch_last_entity, 0, NULL, SMP_T_STR, SMP_USE_INTRN, },
{ "last_rule_file", smp_fetch_last_rule_file, 0, NULL, SMP_T_STR, SMP_USE_INTRN, },