From: Amaury Denoyelle Date: Tue, 8 Apr 2025 14:08:17 +0000 (+0200) Subject: MEDIUM: listener/mux-h2: implement idle-ping on frontend side X-Git-Tag: v3.2-dev11~32 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=52246249ab4d4d948628ace0e0287efb7f7f7fdc;p=thirdparty%2Fhaproxy.git MEDIUM: listener/mux-h2: implement idle-ping on frontend side This commit is the counterpart of the previous one, adapted on the frontend side. "idle-ping" is added as keyword to bind lines, to be able to refresh client timeout of idle frontend connections. H2 MUX behavior remains similar as the previous patch. The only significant change is in h2c_update_timeout(), as idle-ping is now taken into account also for frontend connection. The calculated value is compared with http-request/http-keep-alive timeout value. The shorter delay is then used as expired date. As hr/ka timeout are based on idle_start, this allows to run them in parallel with an idle-ping timer. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 7bcf22cc0..b1f471c6a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -17621,6 +17621,18 @@ id must be strictly positive and unique within the listener/frontend. This option can only be used when defining only a single socket. +idle-ping + May be used in the following contexts: tcp, http, log + + Define an interval for periodic liveliness on idle frontend connections. If + the peer is unable to respond before the next scheduled test, the connection + is closed. Else, the client timeout is refreshed and the connection is kept. + Note that http-request/http-keep-alive timers run in parallel and are not + refreshed by idle-ping. + + This feature relies on specific underlying protocol support. For now, only H2 + mux implements it. Idle-ping is simply ignored by other protocols. + interface Restricts the socket to a specific interface. When specified, only packets received from that particular interface are processed by the socket. This is diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h index 981c94803..2e79a129f 100644 --- a/include/haproxy/connection.h +++ b/include/haproxy/connection.h @@ -716,8 +716,8 @@ static inline int conn_idle_ping(const struct connection *conn) return srv ? srv->idle_ping : TICK_ETERNITY; } else { - /* TODO */ - return TICK_ETERNITY; + struct session *sess = conn->owner; + return sess->listener->bind_conf->idle_ping; } } diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h index becc83b01..7518aa6ec 100644 --- a/include/haproxy/listener-t.h +++ b/include/haproxy/listener-t.h @@ -193,6 +193,7 @@ struct bind_conf { unsigned int analysers; /* bitmap of required protocol analysers */ int maxseg; /* for TCP, advertised MSS */ int tcp_ut; /* for TCP, user timeout */ + int idle_ping; /* MUX idle-ping interval in ms */ int maxaccept; /* if set, max number of connections accepted at once (-1 when disabled) */ unsigned int backlog; /* if set, listen backlog */ int maxconn; /* maximum connections allowed on this listener */ diff --git a/src/listener.c b/src/listener.c index 95a5fef70..c161c885b 100644 --- a/src/listener.c +++ b/src/listener.c @@ -2216,6 +2216,44 @@ static int bind_parse_id(char **args, int cur_arg, struct proxy *px, struct bind return 0; } +/* Parse the "idle-ping" bind keyword */ +static int bind_parse_idle_ping(char **args, int cur_arg, + struct proxy *px, struct bind_conf *conf, + char **err) +{ + const char *res; + unsigned int value; + + if (!*(args[cur_arg+1])) { + memprintf(err, "'%s' expects an argument.", args[cur_arg]); + goto error; + } + + res = parse_time_err(args[cur_arg+1], &value, TIME_UNIT_MS); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument <%s> to <%s> on bind line, maximum value is 2147483647 ms (~24.8 days).", + args[cur_arg+1], args[cur_arg]); + goto error; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument <%s> to <%s> on bind line, minimum non-null value is 1 ms.", + args[cur_arg+1], args[cur_arg]); + goto error; + } + else if (res) { + memprintf(err, "unexpected character '%c' in '%s' argument on bind line.", + *res, args[cur_arg]); + goto error; + } + + conf->idle_ping = value; + + return 0; + + error: + return ERR_ALERT | ERR_FATAL; +} + /* Complete a bind_conf by parsing the args after the address. is the * arguments array, is the first one to be considered.
is * the section name to report in error messages, and and are @@ -2584,6 +2622,7 @@ static struct bind_kw_list bind_kws = { "ALL", { }, { { "backlog", bind_parse_backlog, 1, 0 }, /* set backlog of listening socket */ { "guid-prefix", bind_parse_guid_prefix, 1, 1 }, /* set guid of listening socket */ { "id", bind_parse_id, 1, 1 }, /* set id of listening socket */ + { "idle-ping", bind_parse_idle_ping, 1, 1 }, /* activate idle ping if mux support it */ { "maxconn", bind_parse_maxconn, 1, 0 }, /* set maxconn of listening socket */ { "name", bind_parse_name, 1, 1 }, /* set name of listening socket */ { "nbconn", bind_parse_nbconn, 1, 1 }, /* set number of connection on active preconnect */ diff --git a/src/mux_h2.c b/src/mux_h2.c index 7f99ffd54..86e39e5e2 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -863,6 +863,7 @@ static void h2c_update_timeout(struct h2c *h2c) else { int dft = TICK_ETERNITY; int exp = TICK_ETERNITY; + int ping = TICK_ETERNITY; /* idle connection : no stream, no output data */ if (h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED)) { @@ -904,13 +905,19 @@ static void h2c_update_timeout(struct h2c *h2c) is_idle_conn = 1; } - else if (!(h2c->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) { + else { /* Only idle-ping is relevant for backend idle conn. */ - exp = tick_add_ifset(now_ms, conn_idle_ping(h2c->conn)); - if (tick_isset(exp) && !(h2c->flags & H2_CF_IDL_PING_SENT)) { - /* If PING timer selected, set flag to trigger its emission rather than conn deletion on next timeout. */ - h2c->flags |= H2_CF_IDL_PING; - } + exp = TICK_ETERNITY; + } + + if (!(h2c->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) + ping = tick_add_ifset(now_ms, conn_idle_ping(h2c->conn)); + + exp = tick_first(exp, ping); + /* If PING timer selected, set flag to trigger its emission rather than conn deletion on next timeout. */ + if (tick_isset(exp) && exp == ping && ping != dft && + !(h2c->flags & H2_CF_IDL_PING_SENT)) { + h2c->flags |= H2_CF_IDL_PING; } }