]> git.kaiwu.me - haproxy.git/log
haproxy.git
4 weeks agoMEDIUM: server: implement "purge server" CLI
Amaury Denoyelle [Thu, 17 Sep 2026 13:48:37 +0000 (15:48 +0200)]
MEDIUM: server: implement "purge server" CLI

Define and implement a new CLI command "purge server". This is similar
to "del server", except that deletion is performed in background if it
cannot be completed due to a transient state.

To schedule background deletion, server instance is flagged with
SRV_F_TO_PURGE and inserted into parent proxy <servers_purge> list.
Finally, proxy purge task is scheduled. This will periodically call
srv_check_for_deletion() : on success the server instance is finally
freed (unless its refcount is non null, in which case server free should
occur soon after).

A programmed background server deletion can be cancelled via
srv_cancel_purge() : this simply remove SRV_F_TO_PURGE. This action is
performed when the server state is changed (even to remain in
maintenance).

After purge cancellation, the server will be removed from the parent
proxy list via the task handler. This is not performed directly when
removing the server flag as this may simplify future evolutions, in
particular if thread isolation is reduced and proxy lock becomes
required to manipulate purge list.

4 weeks agoMINOR: proxy: define purge task and purge servers list
Amaury Denoyelle [Thu, 17 Sep 2026 13:48:20 +0000 (15:48 +0200)]
MINOR: proxy: define purge task and purge servers list

Add two new members in proxy type.

First, a task <purge_task> is added. It will be used as an internal task
to cleanup proxy related resources, i.e. its servers and the proxy
itself. The task is allocated only for non internal backend instances.
Handler proxy_process_purge() runs in thread isolation to prevent any
race conditions. This task may run on any thread, thus task_kill() is
used on proxy deinit to ensure runtime backend deletion is still safe.

Second, <servers_purge> is a new list member in proxy. This will contain
any server targeted for a background deletion. Purge task handler will
run itself every second until the list is empty.

For now, task handler is mostly empty and servers purge list is not
used. This will be extended when purge CLI commands for servers and
backends will be implemented.

4 weeks agoMINOR: server: extract unregistering from "delete server" CLI parser
Amaury Denoyelle [Wed, 9 Sep 2026 14:36:46 +0000 (16:36 +0200)]
MINOR: server: extract unregistering from "delete server" CLI parser

Extract the code which performs the server deletion from the "del
server" CLI parser in a new function named srv_unregister(). Its purpose
is to ensure that a server is detached from any visible elements, most
notably the parent proxy list and name tree, and the global GUID tree.
This step is mandatory to remove a server at runtime but is skipped on
deinit.

This reorg allows to implement alternative method for server deletion at
runtime. This will be useful for the future "purge server" CLI command.

4 weeks agoMINOR: server: refactor cli_find_server()
Amaury Denoyelle [Thu, 10 Sep 2026 09:11:42 +0000 (11:11 +0200)]
MINOR: server: refactor cli_find_server()

Refactor cli_find_server() by splitting its execution into two separated
functions. The first one parse_be_srv() is able to extract a backend and
server name from an argument of the form <bename>/<svname>. The second
function find_be_srv() is responsible to perform the lookup of a server
using a backend and server name.

Now cli_find_server() consists mainly of these two calls. The objective
is to be able to perform only the parsing or lookup parts, without
automatic CLI error display. This will be used to extend CLI "wait"
command handler.

CLI parser "delete server" has been updated to use the new
parse_be_srv() as an example of usage. In the future, it could be useful
to update all similar handlers relying on the same argument format.

4 weeks agoMINOR: server: rename <all_servers> list to <servers_to_init>
Amaury Denoyelle [Thu, 17 Sep 2026 09:56:23 +0000 (11:56 +0200)]
MINOR: server: rename <all_servers> list to <servers_to_init>

Now that <all_servers> is only used during postparsing, it is better to
rename it <servers_to_init> to reflect its real purpose.

The attach point in struct server is also renamed to <el_alt> for
"alternative attach point". This is a generic name as it will be reused
for another list storage during runtime.

4 weeks agoOPTIM/MINOR: server: restrict <all_servers> list usage
Amaury Denoyelle [Thu, 17 Sep 2026 09:54:41 +0000 (11:54 +0200)]
OPTIM/MINOR: server: restrict <all_servers> list usage

<all_servers> global list usage is pretty limited. It is only useful at
postparsing to call srv_preinit() on each server instances. It is
possible to simplify it in two ways.

First, dynamic servers are not added anymore into it. For them,
srv_preinit() is called directly on the "add server" handler. Then,
servers are now removed from the global list once postparsing is over.
Thus, <all_servers> is now never used during runtime. This allows to
convert it from a mt-list to a simple list.

Note that log servers and haload servers are created after
check_config_validity(). They must be manually removed from
<all_servers>.

4 weeks agoDOC: configuration: document AWS-LC compatibility restrictions
William Lallemand [Tue, 13 Oct 2026 19:12:05 +0000 (19:12 +0000)]
DOC: configuration: document AWS-LC compatibility restrictions

The configuration manual does not explicitly identify several SSL keywords
and sample fetches that are unavailable with AWS-LC. This leaves users
without compatibility information when selecting an SSL library or
configuring an AWS-LC build.

Add compatibility notes to the affected entries, stating the minimum
OpenSSL version and the lack of AWS-LC support. This is a documentation
change only; runtime behavior is unchanged.

That should fix #3532.

This should be backported to maintained branches with AWS-LC support (as
far as 3.0). Note that some keyword may not exists in previous versions.

4 weeks agoBUG/MINOR: cfgparse-global: only warn when "localpeer" is set together with -L
William Lallemand [Tue, 13 Oct 2026 14:40:36 +0000 (14:40 +0000)]
BUG/MINOR: cfgparse-global: only warn when "localpeer" is set together with -L

Setting "localpeer" in the global section while "-L" is passed on the command
line makes haproxy refuse to load its configuration:

  [ALERT] (447) : config : parsing [haproxy.cfg:428] : 'localpeer' ignored since it is already set by using the '-L' command line argument.
  [ALERT] (447) : config : Error(s) found in configuration file : haproxy.cfg
  [ALERT] (447) : config : Fatal errors found in configuration.

The intent of this check is that "-L" wins and "localpeer" is ignored, and the
documentation still advertises a warning for that case, as did 3.0. The parser
introduced by commit d253f30823 ("MINOR: cfgparse-global: add
cfg_parse_global_localpeer") returns -1 there, which the global keyword
dispatcher turns into an alert and ERR_FATAL. Returning 1 instead makes it emit a
warning and set ERR_WARN, like the other "already specified, continuing" cases of
the same file (pidfile, chroot, tune.*), and the local peer name keeps being taken
from "-L".

Operators who share one configuration file and select the local peer with "-L" on
some instances only can no longer start haproxy at all and must edit their
configuration to work around it, hence the bug tag.

This should be backported to 3.1 and above, where d253f30823 is present; the fix
is a single return value change and applies as-is to 3.1, 3.2, 3.3 and 3.4.

4 weeks agoDOC: make: fix EXTRA_MAKE fragment filename
William Lallemand [Tue, 13 Oct 2026 12:23:50 +0000 (12:23 +0000)]
DOC: make: fix EXTRA_MAKE fragment filename

The EXTRA_MAKE hook includes Makefile.mk, but its documentation comment
still names Makefile.inc. Correct the comment to match the existing
include. This changes no build behavior.

4 weeks agoREGTEST: ssl: require OpenSSL 1.1.1 for crt-list ciphersuites tests
William Lallemand [Tue, 13 Oct 2026 12:16:01 +0000 (12:16 +0000)]
REGTEST: ssl: require OpenSSL 1.1.1 for crt-list ciphersuites tests

The TCP and QUIC crt-list ciphersuites tests require TLSv1.3, which
OpenSSL only supports starting with 1.1.1. Their previous library checks
did not impose this minimum version, allowing the tests to run with
older OpenSSL versions that cannot support the configuration.

Require OpenSSL 1.1.1 or later while retaining AWS-LC support. Keep the
QUIC prerequisites separate from the library check so that AWS-LC does
not bypass the QUIC and backend-support restrictions.

The TCP regression passed with OpenSSL 3.5.5 and the QUIC regression
passed with AWS-LC.

This must be backported wherever commit 38398d2476 ("BUG/MEDIUM: ssl:
crt-list ciphers ignored with AWS-LC") is backported, as far as 3.0.

4 weeks agoREGTESTS: http-messaging: Fix http_fail_cnt_body to succeed with small timeouts
Christopher Faulet [Tue, 13 Oct 2026 06:40:28 +0000 (08:40 +0200)]
REGTESTS: http-messaging: Fix http_fail_cnt_body to succeed with small timeouts

http_fail_cnt_body.vtc script was failing when small timeouts were used. The
script tries to detect client or server aborts during payload forwarding on
the other side. Because "http-no-delay" option was not set, a 200ms delay
could be observed to forward the server response. So with small timeouts,
the server timeout was hit before the client abort. It is now fixed.

In addition the delay before emitting CLI commands was removed.

This patch should be backported as far as 2.8.

4 weeks agoMINOR: stats: report the SSL library FIPS mode in "show info"
William Lallemand [Mon, 12 Oct 2026 14:04:28 +0000 (14:04 +0000)]
MINOR: stats: report the SSL library FIPS mode in "show info"

The "SSL library FIPS mode" indicator printed by "haproxy -vv" had no
equivalent in the CLI. Add a "FipsMode" field to "show info" so that the
FIPS state of the running process can be queried at runtime, using the
same openssl_fips_mode() helper as the -vv line and the fips_mode()
configuration condition.

The field is only emitted by builds with SSL support, and reports 0 when
the SSL library is not in FIPS mode or does not support FIPS mode at all.

4 weeks agoBUG/MEDIUM: mux-h1: only accept the H2 preface on the first request
Christopher Faulet [Wed, 7 Oct 2026 09:53:15 +0000 (11:53 +0200)]
BUG/MEDIUM: mux-h1: only accept the H2 preface on the first request

The H1 to H2 upgrade via the H2 connection preface is only supported for the
first request of a connection. It is checked by testing the absence of
H1C_F_WAIT_NEXT_REQ, which is set on the H1 connection once a first
transaction is finished. But this flag is moved to the H1 stream
(H1S_F_NOT_FIRST) and removed from the connection as soon as a new H1 stream
is created, which happens when the beginning of the next request is
received. So when the preface of a second request is received in several
parts (e.g. "PRI " then the rest), the first part creates an embryonic H1
stream and clears the flag, and the upgrade is then accepted when the
complete preface is received. The connection is upgraded to H2 in the middle
of its life, while the end of the previous response may still be pending in
the output buffer (it is then lost).

Now the H1 stream's H1S_F_NOT_FIRST flag is also checked.

This was introduced by commit 39c7b6b09 ("MEDIUM: mux-h1: Add ST_READY
state for the H1 connections") in 2.4. It may be backported to all
stable versions.

4 weeks agoBUG/MINOR: http-fetch: don't report pseudo-headers in TCP mode
Christopher Faulet [Wed, 7 Oct 2026 14:35:59 +0000 (16:35 +0200)]
BUG/MINOR: http-fetch: don't report pseudo-headers in TCP mode

In TCP mode, HTTP sample fetches parse the raw request found in the channel
buffer using h1_headers_to_hdr_list() before converting it to HTX. But the
H1_MF_NO_PHDR flag was not set, so the ":method" and ":path" pseudo-headers
produced by the parser were added as regular headers to the HTX
message. Thus, for "GET /x HTTP/1.1" with a single Host header, req.hdr_cnt
returns 3, req.hdrs and req.hdr_names contain ":method" and ":path", and
req.hdr(:path) returns the URI, unlike in HTTP mode. It also consumes two
header slots, so that requests with 99 or 100 headers make the fetches fail
in TCP mode while they are accepted in HTTP mode.

Let's set H1_MF_NO_PHDR for this parsing.

This was introduced by commit ef453ed9b ("MINOR: http_fetch: Add
smp_prefetch_htx") in 1.9-dev7. It may be backported to all stable versions.

4 weeks agoBUG/MEDIUM: mux-h1: also release the embryonic stream when the error is empty
Christopher Faulet [Wed, 7 Oct 2026 14:05:00 +0000 (16:05 +0200)]
BUG/MEDIUM: mux-h1: also release the embryonic stream when the error is empty

When an error (400, 408, 500, 501) must be returned by the H1 multiplexer
and the proxy's error message is mapped on /dev/null or an empty file,
h1_send_error() exits without testing for embryonic H1 stream to destroy
it. So the H1 stream remains attached, preventing the H1 connection to be
closed as expected. The H1 connection is only released when the client
timeout expires.

Let's place the label before the embryonic stream destruction, so that it
is also performed for empty errors.

This was introduced by commit 0d4271cda ("BUG/MEDIUM: mux-h1: Properly
handle empty message when an error is triggered") in 3.1-dev7, which was
tagged for backport as far as 2.8. This must be backported where it was.

4 weeks agoMINOR: cache: Remove a useless variable in http_action_req_cache_use
Remi Tricot-Le Breton [Wed, 7 Oct 2026 15:26:34 +0000 (17:26 +0200)]
MINOR: cache: Remove a useless variable in http_action_req_cache_use

The only case in which the 'detached' variable keeps its default value
of 0, the 'res' variable has to be set to NULL so all the checks on
'detached' that happen later are always true since they are called in a
'if (res && res->secondary_key_signature)' block. The 'detached'
variable can then be removed completely.

4 weeks agoBUG/MEDIUM: mux-h2: fix a possible crash on rxbuf allocation failure
Willy Tarreau [Mon, 12 Oct 2026 12:30:18 +0000 (14:30 +0200)]
BUG/MEDIUM: mux-h2: fix a possible crash on rxbuf allocation failure

When receiving a large POST which spans multiple buffers, if the current
rxbuf is full we try to allocate a new buffer. But if the allocation
fails due to an enforced tune.buffers.limit for example, we leave and
try to switch back the buffer to htx via htx_to_buf(htx, scbuf) while
scbuf has become NULL, causing a crash.

Let's just properly reset the htx pointer between each buffer switch
so as not to attempt this operation. This is difficult to reproduce,
but could be triggered once with tune.buffers.limit set to 6, with
1 to 3 pending HTTP/1 requests and a 60kB H2 POST.

This was introduced in 3.1 by commit 4eb3ff1d3 ("MAJOR: mux-h2: make
streams use the connection's buffers"), so the fix must be backported
to 3.2.

4 weeks agoBUG/MEDIUM: mux-h2: report a dead rhttp connection in h2_wake()
Willy Tarreau [Tue, 6 Oct 2026 20:42:47 +0000 (22:42 +0200)]
BUG/MEDIUM: mux-h2: report a dead rhttp connection in h2_wake()

When an outgoing rhttp connection to an rhttp gateway fails, h2_wake()
releases the connection but returns continues to return ret >= 0, making
its caller (sock_conn_iocb() here or the SSL handshake code) continue to
dereference the connection to read conn->flags for example. This is
visible when running with -dMuaf that instantly causes a use-after-free
on a small config like this one:

  frontend fe
      bind "rhttp@be-outgoing/gateway"

  backend be-outgoing
      server gateway 127.0.0.1:1 proto h2

Starting this config is sufficient to make the frontend trigger pre-
connectionns on the backend and cause a crash:

  (gdb) bt
  #0  sock_conn_iocb (fd=<optimized out>) at src/sock.c:1168
  #1  0x0000000000836265 in fd_update_events (fd=<optimized out>, evts=<optimized out>) at src/fd.c:793
  #2  0x000000000051c564 in _do_poll (p=<optimized out>, exp=<optimized out>, wake=<optimized out>) at src/ev_epoll.c:345
  #3  0x0000000000784c3f in run_poll_loop () at src/haproxy.c:3148
  #4  0x000000000078537f in run_thread_poll_loop (data=<optimized out>) at src/haproxy.c:3296
  #5  0x00000000004b5a75 in main (argc=<optimized out>, argv=0x7ffe5682d7a8) at src/haproxy.c:4112

This was introduced in 2.9 by commit 5053e8914 ("MEDIUM: h2: prevent
stream opening before connection reverse completed") so the fix must
be backported to 3.0. Note that this is an experimental feature.

4 weeks agoBUG/MEDIUM: mux-h1: don't pass an upgraded connection to traces in h1s_destroy()
Willy Tarreau [Wed, 7 Oct 2026 03:43:46 +0000 (05:43 +0200)]
BUG/MEDIUM: mux-h1: don't pass an upgraded connection to traces in h1s_destroy()

When an HTTP/1 frontend connection receives the HTTP/2 preface in two
parts, the first one (shorter than a full request) creates an h1s, then
the second one completes the preface and the connection is upgraded to
H2: h1_release() calls conn_upgrade_mux_fe(), h2_init() sets conn->ctx to
the new h2c, then h1_destroy() calls h1s_destroy(), which emits traces
with h1c->conn. h1_trace() and h1_trace_fill_ctx() then derive the h1c
from conn->ctx, which is now the h2c, and dereference it as an h1c,
crashing the worker. h1_release() already takes care of not using the
connection once it doesn't belong to the mux anymore, but h1s_destroy()
does not.

This only happens when h1 traces are enabled (level "state" or above),
and was reproduced with "-dt h1:state:minimal" and a client sending the
H2 preface in two segments split anywhere several ms apart. The process
then crashes in h1_trace().

The fix consists in only passing the connection to the traces in
h1s_destroy() when it still belongs to this h1c. The upgrade with an
existing h1s was made possible in 2.4 by commit 0f9395d81 ("BUG/MAJOR:
mux-h1: Properly handle TCP to H1 upgrades"), while the trace there comes
from commit 6b81df727 ("MINOR: mux-h1/trace: register a new trace source
with its events") in 2.1. This must be backported to all supported
versions.

4 weeks agoBUG/MEDIUM: mux-fcgi: don't return a freed tasklet from fcgi_deferred_shut()
Willy Tarreau [Tue, 6 Oct 2026 20:42:00 +0000 (22:42 +0200)]
BUG/MEDIUM: mux-fcgi: don't return a freed tasklet from fcgi_deferred_shut()

Since 3.2 and commit 8f70f9c04 ("MEDIUM: mux_fcgi: Make sure we return
the tasklet from fcgi_deferred_shut"), fcgi_deferred_shut() returns its
tasklet to the scheduler. But when the deferred shutdown concerns an
orphaned stream, fcgi_strm_destroy() is called, which frees the stream's
shut_tl tasklet, which is precisely the one being run. The scheduler then
clears TASK_RUNNING in the freed tasklet's state, which is a
use-after-free write (and the connection may even have been released).

This is the same issue as the one fixed in mux-h2 by commit "BUG/MEDIUM:
mux-h2: don't return a freed tasklet from h2_deferred_shut()", where it
was reproduced with -dMuaf. Here it's only based on code analysis. It
requires that the FCGI_ABORT or the end of stream record could not be
sent when the stream was shut because the mux buffer was full, and that
the stream detaches before the deferred shutdown is performed, so it's
rare and only the FCGI server side (trusted) is involved.

The fix simply consists in returning NULL once the stream was destroyed.
It must be backported to 3.2.

4 weeks agoBUG/MEDIUM: mux-h2: don't return a freed tasklet from h2_deferred_shut()
Willy Tarreau [Tue, 6 Oct 2026 20:41:23 +0000 (22:41 +0200)]
BUG/MEDIUM: mux-h2: don't return a freed tasklet from h2_deferred_shut()

When h2_deferred_shut() completes the deferred shutdown of an orphaned
stream (the stream connector already left), it destroys the stream with
h2s_destroy(), which also frees the stream's shut_tl tasklet, which is
precisely the one being run. However the function then returns <t>,
which is that freed tasklet, unless the connection was also released.
The scheduler then clears TASK_RUNNING in the freed tasklet's state,
which is a use-after-free write. In default builds the area is generally
still in the thread's pool cache, so the bit being cleared is harmless,
but it may corrupt memory if the area was reused, and it crashes with
-dMuaf.

The situation happens when an RST_STREAM (or an ES) could not be sent
when the stream was shut because the mux buffer was full, and the stream
detaches before the deferred shutdown is performed, e.g. an H2 client
not reading its responses fast enough while a POST request is denied.
It was reproduced with a debug patch simulating a full mux buffer for
the first RST_STREAM, with a POST request blocked by "http-request deny"
and -dMuaf: the worker dies with SIGSEGV in run_tasks_from_lists(), and
works fine with the fix.

The fix simply consists in always returning NULL once the stream was
destroyed. This was introduced in 2.4 by commit 741631414 ("CLEANUP: task:
make sure tasklet handlers always indicate their statuses"), so the fix
must be backported to all supported versions. Note that mux-fcgi's
fcgi_deferred_shut() has the same issue.

4 weeks agoMINOR: hlua: expose PROXY event family through Lua
Aurelien DARRAGON [Wed, 7 Oct 2026 10:42:43 +0000 (12:42 +0200)]
MINOR: hlua: expose PROXY event family through Lua

Add Proxy.event_sub() method to be able to subscribe to per proxy events,
and expose PROXY event family through Lua event_hdl handler.

Documentation was updated with known PROXY family events (ADD/DEL/
PUB/UNPUB).

4 weeks agoMINOR: event_hdl/proxy: publish EVENT_HDL_SUB_PROXY_{ADD,DEL,PUB,UNPUB} events
Aurelien DARRAGON [Tue, 6 Oct 2026 19:26:35 +0000 (21:26 +0200)]
MINOR: event_hdl/proxy: publish EVENT_HDL_SUB_PROXY_{ADD,DEL,PUB,UNPUB} events

Proxy events are published in the per-proxy subscription list as well as
global one. EVENT_HDL_SUB_PROXY_ADD is published when proxy is created
(ie: "add backend"), EVENT_HDL_SUB_PROXY_DEL when backend is removed,
EVENT_HDL_SUB_PROXY_PUB when backend is published and
EVENT_HDL_SUB_PROXY_UNPUB when backend is unpublished.

All events provide event_hdl_cb_data_proxy struct, which contains info
about the proxy that triggered the event.

4 weeks agoMINOR: proxy: add e_sub proxy subscrition list
Aurelien DARRAGON [Tue, 6 Oct 2026 21:08:24 +0000 (23:08 +0200)]
MINOR: proxy: add e_sub proxy subscrition list

add e_sub member to proxy struct so that it becomes possible to register
to PROXY events for a given proxy.

4 weeks agoMINOR: event_hdl: add PROXY family events
Aurelien DARRAGON [Tue, 6 Oct 2026 19:26:22 +0000 (21:26 +0200)]
MINOR: event_hdl: add PROXY family events

Define PROXY event family and 4 initial events: PROXY_ADD / PROXY_DEL /
PROXY_PUB / PROXY_UNPUB, which will be respectively triggered when proxy
is added / removed / published / unpublished.

See dynamic backends documentation for more context.

4 weeks agoBUG/MINOR: hlua: assume proxy_find_by_id() may return NULL on previously existing...
Aurelien DARRAGON [Wed, 7 Oct 2026 10:43:35 +0000 (12:43 +0200)]
BUG/MINOR: hlua: assume proxy_find_by_id() may return NULL on previously existing server

Since 3.4 and dynamic backends, we may hit a race were a backend which
existed at the time the event was published does not exist anymore when
the event is processed. Previously it was assumed the proxy referenced
by a server event would always exist (since proxies couldn't be removed
dynamically), and a BUG_ON() ensured that. But it is not true anymore,
thus we need to check for proxy_find_by_id() NULL return value, which
indicates that the proxy was removed since, and thus it is not worth
looking for the server which triggered the event anyway.

It should be backported to 3.4.

4 weeks agoDOC: filters: Document how to port an existing filter
Christopher Faulet [Thu, 8 Oct 2026 12:19:29 +0000 (14:19 +0200)]
DOC: filters: Document how to port an existing filter

The filters guide now contains a section about the conversion of a legacy
filter to the filter instance mode. It lists the changes from a filter
developer point of view, then explains the minimal changes needed to support
both declaration modes, how to fully use the instance mode, and the pitfalls
and design considerations to keep in mind.

In particular, it details the callback timing and the parsing-time
constraints, the ownership of the filter configuration and of its fields,
the enabled state and its consequences, the class registration and
placement, the legacy mode coexistence, and the lifecycle of the filters
of a proxy created at runtime.

4 weeks agoMEDIUM: filters: Activate the instances inherited by dynamic backends
Christopher Faulet [Wed, 7 Oct 2026 15:03:34 +0000 (17:03 +0200)]
MEDIUM: filters: Activate the instances inherited by dynamic backends

The filter instances of the "add backend" CLI command were copied from the
defaults section but never finalized: the parse callbacks of the inherited
instances were not called, the filter-enable, filter-disable and
filter-sequence directives were not applied, and the flat evaluation orders
were not produced. As a result the new backend had no filter analyzer
enabled and no stream filter instance was attached to the streams routed
through it ("show filter instances" already listed the instances as
"enabled, inherited").

Three helpers are added, called by the "add backend" and "del backend" CLI
commands:

- flt_start_new_proxy() finalizes the instances (parse callbacks,
  filter-enable/filter-disable, filter-sequence, flattening of the three
  evaluation orders) and applies the globally requested filter mode,
  mirroring the pre-check stage of the startup. It is called before
  proxy_finalize(), while the threads are isolated, so that the filter
  analyzers are enabled if the new proxy has filters;

- flt_init_new_proxy() initializes the filters, mirroring the post-check
  and per-thread initialization stages. The 'init' callbacks run on the
  thread performing the creation, as they run on the main thread during
  the startup. The 'init_per_thread' callbacks may initialize thread-local
  variables, so they must run on every thread: they are deferred to each
  thread with twork items, queued while the threads are still parked by
  the thread isolation, and run as their first pending work when they
  resume. The backend is not reachable by the switching rules until it is
  published with "publish backend", which happens after the items
  completed (only "force-be-switch" could select it in-between). Each item
  holds a reference on the proxy, so a concurrent "del backend" cannot
  destroy it before its per-thread callbacks ran;

- flt_deinit_new_proxy() defers the 'deinit_per_thread' callbacks to every
  thread the same way during the deletion, as they may release thread-local
  resources (e.g. references in the per-thread Lua states). The proxy is
  only destroyed once every thread ran its callbacks, when its last
  reference is released.

4 weeks agoBUG/MEDIUM: spoe: Let an agent outlive its applets during a backend deletion
Christopher Faulet [Thu, 8 Oct 2026 10:25:13 +0000 (12:25 +0200)]
BUG/MEDIUM: spoe: Let an agent outlive its applets during a backend deletion

The deletion of a backend holding a SPOE filter destroys the SPOE agent with
the filter deinitialization. But the agent owns an internal frontend proxy,
with its streams and the SPOE applets processing its frames, which are not
attached to the filtered backend: they may outlive the last stream of this
backend, so the agent may be freed while its applets, and the streams of its
applets, still reference it. This is a use-after-free, reachable with the
"del backend" CLI command: no server is required on the filtered backend and
the SPOE streams are attached to the internal frontend of the agent, so an
unpublished backend without attached stream can be deleted while its agent
is still processing frames.

Add a reference count on the SPOE agent: the filter configuration owns one
reference, dropped during its deinitialization, and each applet holds one,
taken when it is created and dropped when its applet context is destroyed,
with the new applet destroy callback. The agent is released when its last
reference is dropped. A backend holding a SPOE filter can now be deleted at
any time: its agents stay alive as long as their filter configuration and
their applets reference them.

The applet reference cannot be dropped with the applet release callback: it
may run during the processing of the applet stream, which may then be
requeued for a last run, for instance while the connection to the agent is
being closed, and dereference the agent internal frontend again, for the log
message of its close for instance. The applet destroy callback is used
instead: it runs during the teardown of the applet stream, from sc_destroy()
inside stream_free(). So the applet stream is guaranteed to never run again.

The internal frontend of the agent, previously embedded in the agent
structure and initialized with setup_new_proxy(), is now allocated with
alloc_new_proxy(), like any other internal proxy, and released with
proxy_drop(), when the agent is released. It may then survive its agent,
which is released during the teardown of the streams of its last applets:
the streams attached to the frontend hold a reference on it during their own
teardown. So the frontend is released after the teardown of its last stream
completed.

This will also be needed for the dynamically created backends, which will be
able to inherit an active SPOE filter instance from their defaults section
and are meant to be deleted at any time.

This patch relies on:

  * MINOR: applet: Add a destroy callback to the applets
  * MEDIUM: stream: Hold a reference on the frontend during stream_free()

This patch should be backported as far as 3.3 with commits above but only
after a period of observation.

4 weeks agoMINOR: applet: Add a destroy callback to the applets
Christopher Faulet [Thu, 8 Oct 2026 10:22:14 +0000 (12:22 +0200)]
MINOR: applet: Add a destroy callback to the applets

The applet ops provide callbacks for the applet lifecycle: an init callback
called before the applet starts processing, a shut callback called when one
side of the applet endpoint is shut, and a release callback called when both
sides are shut. But there is no callback called when the applet context is
destroyed, when its last user is released: the release callback may run long
before, while the stream attached to the applet is still alive, so the
applets holding references on objects used by their stream have no way to
know when to drop them.

Add a destroy callback to the applet ops, called by __appctx_free() when the
applet context is destroyed. It happens during the teardown of the stream
attached to the applet (from sc_destroy(), inside stream_free()). So this is
guaranteed to be after the last run of the stream, even if it was requeued
instead of being immediately freed.

4 weeks agoMEDIUM: stream: Hold a reference on the frontend during stream_free()
Christopher Faulet [Thu, 8 Oct 2026 10:22:10 +0000 (12:22 +0200)]
MEDIUM: stream: Hold a reference on the frontend during stream_free()

The frontend of a stream is dereferenced until the end of stream_free(), in
particular to flush the maximum amount of pools when the proxy is stopping.
But when the stream connectors are destroyed, a client applet attached to
the stream is released there, and it may free its internal frontend (created
owned by the applet), leaving the end of the function with a dangling
frontend.

Take a reference on the frontend before releasing the stream connector and
drop it at the very end of stream_free(), after the last dereference of the
frontend. For the frontends that are never released during the lifetime of
the process, this is a balanced pair of reference counter updates.

This will be needed by the next commits, adding a destroy callback to the
applet ops, called when an applet context is destroyed, which may release
the frontend of its applet stream.

4 weeks agoMINOR: lua: Protect the common Lua state during a runtime filter deinit
Christopher Faulet [Wed, 7 Oct 2026 15:03:30 +0000 (17:03 +0200)]
MINOR: lua: Protect the common Lua state during a runtime filter deinit

hlua_filter_deinit_per_thread() releases the filter reference from the
shared Lua state without taking the global lock. This is fine during the
startup and the shutdown, where it is the only user of the state, but
the proxies dynamically created with "add backend" are destroyed at
runtime with "del backend", with their filter instances finalized: their
deinit callback may then run while other threads execute Lua code on the
shared state. Take the global lock when releasing the reference of the
common state, as all the other runtime users of this state do.

4 weeks agoDOC: config: Review "Supported filters" section
Christopher Faulet [Wed, 7 Oct 2026 14:09:02 +0000 (16:09 +0200)]
DOC: config: Review "Supported filters" section

The section 9 was extended with the "filter-config" syntax of every
supported filter, but the legacy "filter" directive entries were left
unorganized: the decompression directives were dangling at the end of the
HTTP compression subsection, the "filter-config" entries of the SPOE, cache
and fcgi-app filters were separated from their legacy counterparts, and
the descriptions of the legacy directives were missing or misplaced.

Dedicate a subsection to the decompression filter, renumbering the
following ones, and update the cross-references to the renumbered
sections. Move each "filter-config" entry next to the legacy directive it
supersedes, and the historical note about the HTTP compression move from
the deprecated "filter compression" alias entry to the end of the HTTP
compression subsection. Finally, complete the descriptions of the legacy
"filter" directives and format them like the "filter-config" ones.

4 weeks agoMINOR: filters: List the filter classes in haproxy -vv
Christopher Faulet [Wed, 7 Oct 2026 09:09:39 +0000 (11:09 +0200)]
MINOR: filters: List the filter classes in haproxy -vv

The "Available filters" section dumped the keywords registered for the
legacy "filter" directive, one per line with the scope of the keyword
list they come from. New filters will support the new declaration mode
only and will not register such keywords, so the list would be
incomplete. And the printed names are the legacy keyword names, which
are not the filter class names usable with "filter-config" anyway.

Dump the registered filter classes instead, as a comma-separated list,
in their global order, so both declaration modes are covered. Rename
list_filters() to list_filter_classes() and change the header of the
dump accordingly. The legacy keyword list remains reported by
flt_dump_kws(), where a keyword list is what is expected: the "filter"
keyword parsing errors and the "haproxy -dM flt" diagnostics.

Update the "haproxy -vv" example in the internal API documentation and
the sample debug outputs in the regression testing documentation.

4 weeks agoCLEANUP: filters: Use the stream filter instance wording in comments
Christopher Faulet [Wed, 7 Oct 2026 07:01:46 +0000 (09:01 +0200)]
CLEANUP: filters: Use the stream filter instance wording in comments

Now that a filter configuration is called a filter instance, the per-stream
object created from it and attached to a stream is called a stream filter
instance in the documentation. Use the same wording in the comments
describing the filter instance flags, the attach/detach callbacks, the
'struct filter' and the SPOE stop callback.

The "reprensenting" typo in the 'struct filter' comment is fixed as well.

4 weeks agoDOC: lua: Document the filter instance declaration of Lua filters
Christopher Faulet [Wed, 7 Oct 2026 19:18:36 +0000 (21:18 +0200)]
DOC: lua: Document the filter instance declaration of Lua filters

The filter instance mode documentation claims that the supported filter
classes are documented in the section 9 of the configuration manual, but
the Lua filters are missing: they are not built-in classes, they are
registered by the Lua scripts with core.register_filter() and are
declared with the "lua" filter class. The Lua API documentation only
shows the legacy declaration of a Lua filter.

Document the "filter-config lua id <name>" declaration in the Lua API
documentation, next to the legacy one: the instance id selects the
registered filter to instantiate, the optional "enabled" token activates
the instance, and the other arguments are the same as for the legacy
declaration. Mention that contrary to the legacy "filter" directive, the
Lua filters may be declared in defaults sections with the new mode, to
be inherited by the proxies using them. Finally, point to the Lua API
documentation from the introduction of the section 9.

4 weeks agoDOC: filters: Document the filter instance mode and the legacy mode
Christopher Faulet [Tue, 6 Oct 2026 13:54:54 +0000 (15:54 +0200)]
DOC: filters: Document the filter instance mode and the legacy mode

The filter API was reworked to support filter instances declared with
"filter-config" and referring to a filter class, while the historical
"filter" directive is still supported through the legacy mode. Update the
documentation accordingly.

In the internal API documentation, define the new wording: a "filter
instance" is the configuration entity declared in a proxy, which is
user-visible, and a "stream filter instance" is the per-stream entity
attached to a stream, represented by 'struct filter' in the code.

Explain the two declaration modes and how a proxy selects one of them: the
explicit declarations select the mode and a proxy using only implicit
filters works in the new mode, unless the "filter-api" global option is set
to "downgrade-to-legacy". Describe how to write a filter for the new mode:
the filter class, its registration and placement, the instance parse
callback and its constraints. In particular, the instances are finalized
after the whole configuration was parsed, their configuration is shared and
read-only, they are disabled by default and they may be implicit or
inherited. Explain how the legacy mode is kept working and how the parsing
code can be shared between both modes with flt_parse_instance_legacy().

Update the descriptions of the filter configuration and instance structures,
document the helpers used to reference a filter configuration from other
directives (flt_has_filter(), flt_foreach_conf() and flt_find_conf()) and
add the FAQ section announced by the summary.

In the configuration manual, document the "filter-config", "filter-enable",
"filter-disable" and "filter-sequence" directives, their interaction with
the legacy "filter" directive, the two declaration modes, the
"legacy-filter-mode" global option enforcing the legacy mode on the proxies
without explicit declaration, and the "filter-config" syntax of every
supported filter. In the management manual, document the "show filter
classes", "show filter instance" and "show filter instances" CLI commands.

4 weeks agoMINOR: filters: Add a global option to enforce the legacy filter api
Christopher Faulet [Thu, 8 Oct 2026 13:38:46 +0000 (15:38 +0200)]
MINOR: filters: Add a global option to enforce the legacy filter api

A proxy without explicit filter declaration uses the new filter mode by
default. It is an issue for the obsolete filters relying on an implicit
declaration only and not supporting the new mode: the implicit declarations
of such filters are not visible for the new mode, they are silently
ignored. All the filters supported in-tree are available in both modes, but
it is not necessarily the case of the third-party filters, and there was no
way for the user to restore the prior behavior in this case.

Add the "filter-api" global option for this: when it is set to
"downgrade-to-legacy", the legacy filter mode is enforced on the proxies
without explicit filter declaration, "filter" or "filter-*" directives. The
proxies with explicit declarations keep the mode they selected, so both
modes can be used simultaneously, on different proxies.

The option is unset by default. It can be set to "default" to restore the
default mode.

4 weeks agoREGTESTS: filters: Check non-HTX Lua filters in HTTP chains
Christopher Faulet [Fri, 2 Oct 2026 19:32:14 +0000 (21:32 +0200)]
REGTESTS: filters: Check non-HTX Lua filters in HTTP chains

Configure enabled non-HTX Lua rewrites alongside compatible trace and
bandwidth filters in HTTP mode. Check that neither request nor response
bodies are rewritten and the compatible chain still forwards correctly,
with both instances and legacy declarations.

Reuse the TCP rewrite fixture so the compatibility check complements its
positive TCP payload tests.

4 weeks agoREGTESTS: filters: Record Lua callback waits across backend EOF
Christopher Faulet [Fri, 2 Oct 2026 19:28:21 +0000 (21:28 +0200)]
REGTESTS: filters: Record Lua callback waits across backend EOF

While exercising callback resumption, a complete chunked response could
be lost when the backend closed while Lua filters were waiting. The
related EOF race was also observed with legacy filters on the pre-series
build; it is independent of instance parsing and sequencing.

Keep an EOF reproducer separate from the passing resumption test, which
uses a barrier to hold the backend open. Exercise legacy and instance
chains and keep-alive reuse, and mark the test broken until the EOF race
is fixed. Allow the shared probes to configure their wait delay.

4 weeks agoREGTESTS: filters: Exercise per-thread Lua instance state
Christopher Faulet [Fri, 2 Oct 2026 19:23:06 +0000 (21:23 +0200)]
REGTESTS: filters: Exercise per-thread Lua instance state

Load the HTTP probes separately on each thread and inherit frontend
instances into listeners pinned to different threads. Check the Lua
state id and per-state construction counters through frontend and backend
callbacks, including a second stream on each thread.

Verify that instances, disabled instances and independent response
sequences are handled correctly without sharing mutable Lua state between
threads. Extend the probe headers with the Lua state id.

4 weeks agoREGTESTS: filters: Exercise TCP Lua payload chains and sequencing
Christopher Faulet [Fri, 2 Oct 2026 19:21:42 +0000 (21:21 +0200)]
REGTESTS: filters: Exercise TCP Lua payload chains and sequencing

Use same-length, non-commuting Lua substitutions to observe the actual
TCP request and response evaluation order without depending on packet
boundaries. Mix the rewrites with trace and active bandwidth filters.

Inherit independent channel sequences from named defaults, reverse them
locally by first detaching the inherited child to the class anchor, and
check class-wide disabling followed by id-specific enabling. Include a
fully disabled Lua chain and a legacy chain retaining symmetric order.

Use HTTP framing only to check complete bodies over TCP; the proxies do
not parse HTTP and the Lua filters are not HTX-capable.

4 weeks agoREGTESTS: filters: Exercise Lua probes around mixed built-in filters
Christopher Faulet [Fri, 2 Oct 2026 19:19:11 +0000 (21:19 +0200)]
REGTESTS: filters: Exercise Lua probes around mixed built-in filters

Inherit a chain combining Lua, trace, cache, compression and bandwidth
filters from named defaults. Position Lua probes before and after request
and response compression and verify the encoding each probe observes.
Activate both bandwidth directions and validate the forwarded bodies.

Populate the cache with an uncompressed body, replay it to a client not
accepting gzip and verify that the response probes still run on a hit.
Override compression and response bandwidth state on a sibling backend
without changing the enabled sibling or the inherited Lua probes.

4 weeks agoREGTESTS: filters: Exercise Lua filters across HTTP retries
Christopher Faulet [Fri, 2 Oct 2026 19:18:00 +0000 (21:18 +0200)]
REGTESTS: filters: Exercise Lua filters across HTTP retries

Retry a 503 response and redispatch to a second server. Check that both
servers receive the same request header order and instance identifiers,
without another attachment or duplicate request processing.

Verify that the failed response does not leak headers into the final
response, and that its filter order and instances still follow the
frontend/backend response sequences. Include a waiting frontend filter.

4 weeks agoREGTESTS: filters: Exercise Lua callback resumption
Christopher Faulet [Fri, 2 Oct 2026 19:18:00 +0000 (21:18 +0200)]
REGTESTS: filters: Exercise Lua callback resumption

Chain waiting and non-waiting Lua filters on the frontend and backend,
with different global, request and response orders. Return WAIT once in
start/header callbacks and verify that resumption does not duplicate
headers, recreate instances or lose the independently ordered lists.

Combine the waits with small payload forwarding quotas, keep-alive and
chunked request/response bodies. Use a barrier to keep the backend open
until the client drains the final response; EOF handling is independent
of the resumption behavior checked here.

4 weeks agoREGTESTS: filters: Exercise stream isolation across backend modes
Christopher Faulet [Fri, 2 Oct 2026 19:14:57 +0000 (21:14 +0200)]
REGTESTS: filters: Exercise stream isolation across backend modes

Reuse a client connection while switching between instance-mode, legacy
and fully disabled backends, then open another client connection. Check
that every transaction gets new instances, construction follows global
order, response order stays independent, and no backend links or state
leak into a later request.

Include disabled instances, declined attachment, request and response
payload filtering with small forwarding quotas, and a chunked request.
Validate request byte counts and bodies as well as callback order. Reuse
the instance backend's connection across transactions and clients.

4 weeks agoREGTESTS: filters: Exercise Lua inheritance and state selection
Christopher Faulet [Fri, 2 Oct 2026 19:10:36 +0000 (21:10 +0200)]
REGTESTS: filters: Exercise Lua inheritance and state selection

Add reusable Lua probes exposing construction order, channel order,
encoding visibility and request payload counts. Keep callback state on
the stream instance, and support declined attachment, bounded payload
forwarding and resumable callbacks for subsequent tests.

Exercise named defaults derived from plain defaults, independent frontend
and backend inheritance, local instance replacement and sibling
isolation. Check class-wide and id-specific enabling/disabling, last-match
precedence, directives preceding instance replacement and independent
global/request/response sequences. Also forward traffic through a pair of
proxies with all instances disabled.

Check behavior on the wire and inspect selected instance states through
the CLI, rather than relying on configuration acceptance alone.

4 weeks agoREGTESTS: filters: Add a test for per-proxy stream filter ordering
Christopher Faulet [Fri, 2 Oct 2026 16:06:33 +0000 (18:06 +0200)]
REGTESTS: filters: Add a test for per-proxy stream filter ordering

Add a Lua filter exposing the attach order with a per-proxy ordinal and
the request/response evaluation order in a header. The reg-test checks
that instance-mode filters are attached in the global order and linked
to the request and response channels in their own order, that disabled,
declined and non-HTX filters are skipped, and that the legacy or
instance model is selected independently for each proxy, including
mix-and-match frontends and backends, and a listen proxy.

4 weeks agoMINOR: spoe: Use the filter configuration helpers for lookups and checks
Christopher Faulet [Fri, 2 Oct 2026 16:06:30 +0000 (18:06 +0200)]
MINOR: spoe: Use the filter configuration helpers for lookups and checks

Rework spoe_check() to check the uniqueness of the SPOE engine ids with
flt_foreach_conf() in global order instead of scanning the legacy
filter_configs list.

Resolve the SPOE engine used by a "send-spoe-group" rule with
flt_find_conf() and a match callback. This works with both the legacy
configurations and the filter instances.

4 weeks agoMINOR: flt-http-comp: Use the filter helpers for implicit compression checks
Christopher Faulet [Fri, 2 Oct 2026 16:06:28 +0000 (18:06 +0200)]
MINOR: flt-http-comp: Use the filter helpers for implicit compression checks

Rework check_implicit_http_comp_flt() to walk the filter configurations
with flt_foreach_conf() in global order and keep the request/response
detection, the cache ordering and the explicit declaration detection in a
dedicated callback.

This makes the implicit request and response compression filters work with
the filter instances, where the legacy filter_configs list is empty. In
instance mode the implicit declaration rules are enforced by the
instance parsers, so the legacy-specific checks are skipped.

The locally declared implicit filter configurations preserve the previous
behavior and are appended at the end of the legacy list.

4 weeks agoMINOR: flt-decomp: Use the filter helpers for implicit decompression checks
Christopher Faulet [Fri, 2 Oct 2026 16:06:25 +0000 (18:06 +0200)]
MINOR: flt-decomp: Use the filter helpers for implicit decompression checks

Rework check_implicit_decomp_flt() to walk the filter configurations with
flt_foreach_conf() in global order and keep the request/response detection,
the cache ordering and the explicit declaration detection in a dedicated
callback.

This makes the implicit request and response decompression filters work
with the filter instances, where the legacy filter_configs list is empty.
In instance mode the implicit declaration rules are enforced by the
instance parsers, so the legacy-specific checks are skipped.

The locally declared implicit filter configurations preserve the previous
behavior and are inserted at the beginning of the legacy list.

4 weeks agoMINOR: bwlim: Look up the bandwidth filter with the filter helpers
Christopher Faulet [Fri, 2 Oct 2026 16:06:21 +0000 (18:06 +0200)]
MINOR: bwlim: Look up the bandwidth filter with the filter helpers

Resolve the set-bandwidth-limit filter with flt_find_conf() and a match
callback on the bandwidth limitation class and the filter name. This works
with both the legacy configurations and the filter instances, and it
fixes the lookup that could return a non-matching bandwidth filter when no
filter matched.

4 weeks agoMINOR: fcgi-app: Use the filter configuration helpers for lookups and checks
Christopher Faulet [Fri, 2 Oct 2026 16:06:19 +0000 (18:06 +0200)]
MINOR: fcgi-app: Use the filter configuration helpers for lookups and checks

Look up the FCGI filter configuration of a proxy with flt_find_conf()
instead of scanning the legacy filter_configs and then falling back to the
finalized instances. The helper already selects the right source
according to the proxy mode.

Rework fcgi_flt_check() to walk the filter configurations with
flt_foreach_conf() and check the uniqueness of the FCGI application and
the implicit declaration rules in a dedicated callback.

The callback now considers any second FCGI filter of the proxy as an
error, including one referring to the same application, since a single
FCGI filter remains supported per proxy.

4 weeks agoMINOR: cache: Use the filter helpers for cache checks and post-check
Christopher Faulet [Fri, 2 Oct 2026 16:06:15 +0000 (18:06 +0200)]
MINOR: cache: Use the filter helpers for cache checks and post-check

Rework cache_store_check() to walk the filter configurations with
flt_foreach_conf() in response order instead of iterating the legacy
filter_configs directly. This lets the ordering checks work with the
filter instances, and the legacy checks are skipped in instance mode
since they are handled by the instance parsers.

While here, also accept the decompression filters in the implicit cache
declaration, and reject a response decompression filter placed after the
cache, mirroring the existing compression check.

Same conversion for post_check_cache(), which now references the cache in
matching cache filter configurations through flt_foreach_conf().

4 weeks agoMEDIUM: cache: Resolve cache rules during the check stage
Christopher Faulet [Fri, 2 Oct 2026 16:06:07 +0000 (18:06 +0200)]
MEDIUM: cache: Resolve cache rules during the check stage

A "cache-store"/"cache-use" rule needs the cache-store filter configuration
with the same cache name. Resolving it while parsing only works with the
legacy filter configurations, because the filter instances are not
finalized yet.

Store the cache name in the rule during parsing, keep creating the implicit
filter instance, and resolve the configuration from the rule check
callback with flt_find_conf(). This works in both modes: legacy mode still
creates the legacy configuration on demand as before, and instance mode
finds the finalized instance.

The name is now owned by the rule until the check stage, and by the legacy
configuration afterwards.

4 weeks agoMEDIUM: filters: Attach filter instances to streams
Christopher Faulet [Fri, 2 Oct 2026 16:05:58 +0000 (18:05 +0200)]
MEDIUM: filters: Attach filter instances to streams

Instantiate the filter instances of a proxy when a stream is created
(frontend) or bound to a backend. Each enabled instance is instantiated
once, in the global flattened order, using flt_stream_add_proxy_filters().

In legacy mode the historical behavior is preserved: every filter is
immediately appended to both the request and the response filter lists.
In instance mode, the instance links are initialized but not appended,
then the request and response lists are built in independent passes that
follow the per-side instance order. Instances that were skipped because
of an incompatible payload type or because their attach callback declined
are simply not found when linking, so they end up on no channel list.

Instances are shared between streams, so the code never stores per-stream
pointers in them; the instance is looked up by its unique configuration
pointer instead. Update the comment on struct filter to state that the
request and response links are now independent.

4 weeks agoMINOR: filters: Select legacy or instance model in lifecycle callbacks
Christopher Faulet [Fri, 2 Oct 2026 16:05:49 +0000 (18:05 +0200)]
MINOR: filters: Select legacy or instance model in lifecycle callbacks

Make flt_init(), flt_init_per_thread(), flt_check(), flt_check_instances(),
flt_deinit() and flt_deinit_per_thread() process a single configuration
model, chosen with flt_use_legacy_filter(): the legacy filter configurations
or the finalized filter instances.

Instances are only initialized, checked and released in instance mode.
Legacy configurations are still released unconditionally because rules may
keep referencing them in instance mode, but their callbacks are not run when
instances were selected.

Previously both lists were always processed. This prepares attaching the
instances to streams without running startup callbacks twice.

4 weeks agoMINOR: filters: Add flt_has_filter() and use it in proxy_finalize()
Christopher Faulet [Fri, 2 Oct 2026 16:05:46 +0000 (18:05 +0200)]
MINOR: filters: Add flt_has_filter() and use it in proxy_finalize()

Add flt_has_filter() returning whether a proxy has at least one filter,
from the legacy configurations or from the flattened instances. Use it
in proxy_finalize() instead of testing px->filter_configs directly, so
the filter analyzers are also enabled for proxies using the filter
instances.

proxy_finalize() runs after the filter instances are flattened, so the
helper can safely inspect px->filter_instances.

4 weeks agoMINOR: filters: Add helpers to iterate and find filter configurations
Christopher Faulet [Fri, 2 Oct 2026 16:05:29 +0000 (18:05 +0200)]
MINOR: filters: Add helpers to iterate and find filter configurations

Add flt_foreach_conf() to walk the filter configurations of a proxy in
the evaluation order of a given side, and flt_find_conf() to look up the
first configuration matching an id and an optional callback.

Both helpers select the legacy filter configurations (px->filter_configs)
when the proxy is in legacy mode, and the finalized filter instances
otherwise, following the global, request or response order. They will let
the filter callbacks and the action parsers work in both modes without
duplicating the list selection.

The helpers are not used yet.

4 weeks agoMINOR: filters: Don't finalize filter instances in legacy mode
Christopher Faulet [Mon, 5 Oct 2026 07:06:24 +0000 (09:06 +0200)]
MINOR: filters: Don't finalize filter instances in legacy mode

flt_precheck_instances() finalized every instance of a proxy,
calling the parser of its filter class to produce the filter
configuration. But in legacy mode, the instances mirrored from the
implicit declarations are only metadata: the filters and the
implicit/explicit reconciliation are fully handled by the legacy
filter_configs list, checked and completed by flt_check().

Running the class parsers on these instances would be wrong: they may
modify the proxy options (e.g. the decompression parsers update the
direction flags of the proxy) or allocate filter configurations that
are never used by the legacy evaluation path. And the filter-enable and
filter-sequence directives are rejected in legacy mode, so nothing else
consumes the instances during the post-parsing stage.

Now flt_precheck_instances() returns early for a proxy using the
legacy "filter" directive. Its instances keep no filter configuration:
they are only flattened as metadata, for inspection through the CLI, and
released with the proxy. The legacy filters, including the implicit
decompression and compression filters, remain checked and initialized
as before.

4 weeks agoMINOR: filters: Use a proxy flag to detect use of legacy 'filter' directive
Christopher Faulet [Fri, 2 Oct 2026 09:44:06 +0000 (11:44 +0200)]
MINOR: filters: Use a proxy flag to detect use of legacy 'filter' directive

Replace the FLT_CFG_FL_LEGACY flag set on each filter configuration with
the PR_FL_FILTER_LEGACY proxy flag, set when an explicit legacy "filter"
directive is parsed.

The legacy or instance mode is a property of the proxy, not of an
individual filter configuration, so flt_check_config_mode() no longer
needs to walk px->filter_configs to detect a legacy filter. Add the
inline helper flt_use_legacy_filter() to expose the proxy flag and use it
in flt_check_config_mode().

This prepares selecting the legacy filter configurations or the filter
instances on a per-proxy basis.

4 weeks agoMINOR: filters: Expose global filter ordering through the CLI
Christopher Faulet [Thu, 1 Oct 2026 05:26:44 +0000 (07:26 +0200)]
MINOR: filters: Expose global filter ordering through the CLI

Accept global for show filter classes and show filter instances, and show
all three orders when no direction is selected. Include global in single
instance details. Traverse unflattened class references for defaults as
well as flat lists for regular proxies.

Share class dumping across the three orders and instance dumping across
flat lists and class-reference trees. Reset separators for each order and
emit one complete line per order, including empty lists.

Extend the regression test to check the global registry order, independent
request/response orders, reordered and inherited instances, defaults,
lookup without double counting, enabled state and invalid CLI directions.

4 weeks agoMINOR: filters: Support global instance sequencing
Christopher Faulet [Thu, 1 Oct 2026 05:26:37 +0000 (07:26 +0200)]
MINOR: filters: Support global instance sequencing

Accept global alongside request and response in filter-sequence. Global
constraints use the same class/instance references and loop checks, but
may order instances whose classes do not share a channel side. Moving a
instance in this order leaves its request and response positions alone.

Add native VTC coverage for global moves, inherited sequences and local
overrides. Reject self-references and cycles in all three orders, including
further lookups after a rejected move, with -conf-BAD instances for the
configuration diagnostics. CLI assertions are added with the inspection
commands in the next patch.

4 weeks agoMINOR: filters: Use global instance order for startup operations
Christopher Faulet [Thu, 1 Oct 2026 05:26:28 +0000 (07:26 +0200)]
MINOR: filters: Use global instance order for startup operations

Use the global instance order for operations that have no channel side:
explicit-configuration detection, lookup, defaults inheritance, enabling,
post-parsing, checks, initialization and deinitialization, including the
per-thread callbacks.

Every instance now appears exactly once in these traversals, including
request-only and response-only classes. This removes the request-first
ordering and the response-side duplicate suppression. Per-channel lists
remain independent for side-specific processing; stream attachment is not
changed by this patch.

4 weeks agoMINOR: filters: Maintain a global instance list per proxy
Christopher Faulet [Thu, 1 Oct 2026 05:26:21 +0000 (07:26 +0200)]
MINOR: filters: Maintain a global instance list per proxy

Add FLT_SIDE_GLOBAL and a global ordering link to each filter instance.
Create px->conf.filter_refs in registry order alongside filter_req_refs and
filter_res_refs, and flatten these references into px->filter_instances.
Every instance belongs to the global order as well as its applicable
channels.

Use common ordering helpers for traversal, class references, flattening and
sequence moves. Initialize all instance links on allocation, and unlink both
channel trees before releasing instances through the global owner list.
Initialize directive links before fallible parsing/copying operations.  Roll
back a sequence move that creates a cycle so subsequent lookups and cleanup
retain reachable, loop-free trees.

The global list is maintained here, but side-independent startup operations
still traverse request/response orders until the next patch. The parser and
CLI do not expose global sequencing or inspection yet.

4 weeks agoMINOR: filters: Reject mixing legacy and filter-* directives
Christopher Faulet [Wed, 30 Sep 2026 16:44:05 +0000 (18:44 +0200)]
MINOR: filters: Reject mixing legacy and filter-* directives

Do not allow filter-config, filter-enable/filter-disable or filter-sequence
together with an explicit legacy filter directive in the same proxy,
regardless of parsing order. A legacy filter must also be rejected when the
proxy inherits explicit filter-* directives from its defaults section.

Mark configurations created by the legacy filter parser so implicit legacy
filters do not select a mode. Share the explicit-instance/directive check
with proxy_ref_defaults(), ignoring FLT_INST_F_IMPLICIT instances on both
sides. Check for conflicts before invoking parsers or recording directives.

Add native VTC coverage for all four directives, both declaration orders,
inherited directives and response-only instances. Also check that each mode
permits multiple declarations, different proxies can use different modes,
and implicit cache filters can coexist with either explicit syntax.

4 weeks agoMINOR: filters: Restrict defaults inheritance with explicit filter directives
Christopher Faulet [Wed, 30 Sep 2026 16:36:55 +0000 (18:36 +0200)]
MINOR: filters: Restrict defaults inheritance with explicit filter directives

In proxy_ref_defaults(), reject listen and defaults sections inheriting from
a defaults section containing explicit filter instances, enable/disable
entries or sequence directives. Frontends and backends must not share such a
defaults section either; record the first inheriting proxy's type in the
defaults capabilities, as already done for TCP/HTTP rules.

Inspect instances on both request and response sides, ignoring those marked
FLT_INST_F_IMPLICIT. Class references alone do not imply an explicit filter
configuration. Multiple proxies of the same type remain allowed, and
implicit instances alone do not restrict inheritance. Perform these checks
before copying instances.

Update the named-defaults acceptance test to use two frontends. Add native
VTC coverage for explicit instances and each directive independently,
including response-only instances, both frontend/backend declaration orders,
listen and defaults rejection, and allowed inheritance.

4 weeks agoMINOR: filters: Reject filter-* directives in anonymous defaults
Christopher Faulet [Wed, 30 Sep 2026 16:24:00 +0000 (18:24 +0200)]
MINOR: filters: Reject filter-* directives in anonymous defaults

Require a named defaults section for filter-config, filter-sequence and
filter-enable/filter-disable, consistently with the restriction on TCP/HTTP
rules. Reject these directives before parsing their arguments or recording
configuration entries. Named defaults and regular proxies remain supported.

Add regression coverage for each rejected directive and for valid usage in
named defaults, frontends and backends, including inherited instances.

4 weeks agoMINOR: filters: Expose classes and instances through the CLI
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MINOR: filters: Expose classes and instances through the CLI

Add operator-level commands to inspect the request/response class order,
list a proxy's flat instance order, and inspect a single instance by
class and optional id. Display its enabled and implicit/inherited state,
source location, sides and raw arguments.

Keep these diagnostics separate from the configuration and ordering changes.
They report the new instance model, not the still-legacy stream evaluation
path. This completes the split of the current WIP without changing its final
source contents or completing runtime integration.

4 weeks agoMINOR: filters: Mirror implicit filters as filter instances
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MINOR: filters: Mirror implicit filters as filter instances

Add a helper to record an enabled implicit instance without replacing an
existing instance of the same class and id. Copy its raw arguments and
source location, and mark it as implicit so an explicit declaration can
replace it.

Create these instances alongside legacy filters for cache rules,
use-fcgi-app, and implicit compression/decompression. Cache and FCGI use the
cache/application name as the id; compression and decompression need none.
Allow the legacy FCGI configuration and a instance to refer to the same
application even though their configuration pointers differ.

Legacy filters are deliberately retained: reconciling the two models and
selecting which one is attached to streams remain unfinished.

4 weeks agoMINOR: filters: Add per-side filter-sequence constraints
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MINOR: filters: Add per-side filter-sequence constraints

Add filter-sequence request/response entries using class[/id] references and
':before()' or ':after()' to place a instance before or after a class or
another instance. Store source locations and inherit entries from defaults
in configuration order.

Apply the constraints before flattening the class trees. Ignore absent
source instances and sources without the selected side, but validate
reference entities and require ids for ambiguous source instances. Check for
loops after each move and report the originating directive on failure.

Keep sequence parsing, application, inheritance and cleanup together so the
ordering feature can be reviewed independently of instance parsing and state
selection. The resulting order is not yet used for stream attachment.

4 weeks agoMINOR: filters: Add filter-enable and filter-disable directives
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MINOR: filters: Add filter-enable and filter-disable directives

Record enable/disable requests by class and optional instance id, with
source locations for diagnostics. Inherit these entries from defaults before
local entries, and apply them in configuration order after instances have
been parsed so forward references are possible.

Without an id, update every instance of the class. Reject unknown classes
and missing instances. Free consumed entries during flattening and handle
them in proxy cleanup and inheritance error paths.

These directives update instance state only; attaching enabled instances
to streams remains part of the unfinished runtime integration.

4 weeks agoMEDIUM: filters: Parse and finalize filter-config instances
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MEDIUM: filters: Parse and finalize filter-config instances

Add filter-config with a class, optional id, enabled marker and raw class
arguments. Require identifiers for classes allowing multiple instances,
reject duplicate local declarations, and allow inherited or implicit ones to
be replaced locally.

After configuration parsing, validate class multiplicity, call each class
parser once per instance and flatten the per-side class trees. Extend check,
init, per-thread and deinit callbacks to the finalized instances, without
processing instances present on both sides twice. Let FCGI lookup find
finalized instances when no legacy filter is configured.

Legacy filters and instances coexist, and the stream evaluation path is not
switched to instances by this patch. The enabled state is recorded but
runtime attachment and legacy/instance selection remain to be implemented.

4 weeks agoMINOR: filters: Add instance parsers for the built-in classes
Christopher Faulet [Wed, 30 Sep 2026 16:10:22 +0000 (18:10 +0200)]
MINOR: filters: Add instance parsers for the built-in classes

Connect built-in classes to parsers that produce a instance's flt_conf
without attaching it to the legacy evaluation list.

Provide a bridge to legacy keyword parsers, translating the stored argument
vector to their empty-string-terminated convention and rejecting leftover
arguments. Use it for trace, bandwidth limitation, SPOE and Lua; restore
curproxy around SPOE parsing and resolve Lua filters by instance id.

Use dedicated parsers for cache, FCGI, compression and decompression so
their legacy implicit/explicit reconciliation does not modify
filter_configs.  Cache and FCGI application names are supplied by the
instance identifier.  The filter-config directive and instance finalization
follow separately.

4 weeks agoMINOR: filters: Inherit filter instances from defaults sections
Christopher Faulet [Wed, 30 Sep 2026 16:10:21 +0000 (18:10 +0200)]
MINOR: filters: Inherit filter instances from defaults sections

Deep-copy raw filter instances when a proxy references its defaults
section, before parsing the proxy's own directives. Preserve the class,
identifier, enabled state and source location, while marking the copy as
inherited so a later local declaration can replace it.

Add the helper that links a instance to its proxy's class references.
Copies own their arguments and are released independently of the defaults
proxy. On inheritance failure, release the partially copied instances.

4 weeks agoMINOR: filters: Add per-proxy filter instance storage and lookup
Christopher Faulet [Wed, 30 Sep 2026 16:10:21 +0000 (18:10 +0200)]
MINOR: filters: Add per-proxy filter instance storage and lookup

Prepare a instance model separate from the instantiated legacy filter
configurations. Keep raw arguments and source locations, and anchor each
instance in the class references for the sides on which its class exists.
Per-side flat lists will hold the finalized order later.

Create class references when setting up non-internal proxies, following the
global class order. Add instance traversal and lookup helpers, plus cleanup
for both the class-reference trees and flat lists on proxy destruction.
Prepare per-side links in flt_conf for the later runtime integration.

No configuration directive creates instances yet, and stream attachment
remains unchanged.

4 weeks agoMINOR: filters: Introduce filter classes and per-side class ordering
Christopher Faulet [Wed, 30 Sep 2026 16:10:21 +0000 (18:10 +0200)]
MINOR: filters: Introduce filter classes and per-side class ordering

Introduce a class registry distinct from the legacy filter keyword registry.
A class has a unique name, some flags, a instance parser and independent
request/response placement relative to other classes.

Export the built-in class names from their implementations and establish
their default order centrally. Provide registration and placement helpers
for additional classes, and reject classes placed on neither side.

Leave built-in instance parsers unset until the parser adapters are added.
This is configuration infrastructure only; stream evaluation still uses the
legacy filters. This WIP registry also unconditionally references the Lua
class: support for builds without Lua remains to be completed.

4 weeks agoMINOR: fcgi: Move application validation to the post-check stages
Christopher Faulet [Wed, 30 Sep 2026 16:10:21 +0000 (18:10 +0200)]
MINOR: fcgi: Move application validation to the post-check stages

Filter instances will be finalized during configuration post-parsing.  FCGI
proxy validation must run after that stage to see applications supplied by
instances, rather than depending on configuration postparser ordering.

Split the existing postparser into a per-proxy post-check for the HTTP mode,
server mux and request header policy checks, and a global post-check for
application finalization. Instance lookup itself is added with the
filter-config directive in a later patch.

4 weeks agoMINOR: bwlim: Use a different filter ID for in/out filters
Christopher Faulet [Thu, 24 Sep 2026 20:30:11 +0000 (22:30 +0200)]
MINOR: bwlim: Use a different filter ID for in/out filters

The in or out bandwidth limitation can be considered as two different kind
of filters. They rely on same functions but they are applied on different
channels. In addition, the filter keywords are already oriented. So it is
better to also use a different filter id.

4 weeks agoMINOR: flt-decomp: Don't instantiate a filter without applicable direction
Christopher Faulet [Mon, 5 Oct 2026 14:53:37 +0000 (16:53 +0200)]
MINOR: flt-decomp: Don't instantiate a filter without applicable direction

decomp_strm_init() asserted that the filter configuration was never
NULL. This holds for the legacy filter configurations, which always carry
the decompression direction of their proxy. But an implicit instance
inherited by a proxy without the matching capability will keep its metadata
while having no applicable direction, leaving its configuration NULL when
the instance is finalized.

Drop the assertion and handle a NULL configuration like a direction disabled
with the "none" mode or without any algorithm: the filter is not attached to
the stream and the instance remains as metadata. The decompression behavior
is unchanged, such a filter was already ignored on every stream.

4 weeks agoMINOR: flt-http-comp: Don't uselessly init a compression filter
Christopher Faulet [Mon, 5 Oct 2026 06:30:05 +0000 (08:30 +0200)]
MINOR: flt-http-comp: Don't uselessly init a compression filter

When a compression filter, request or response, is attached to a stream,
there was no special test to know if the filter will be usable, so when its
direction was not enabled or no algorithm was available for it on the proxy
that declared it.

Now the attach callback returns 0 when the direction is not enabled or when
the compression configuration has no algorithm for the filter's direction,
so the filter is not attached to the stream at all. Its per-stream state is
not allocated and its HTTP callbacks are never called; these filters were
already ignored by the http_headers callback, they are just not instantiated
anymore.

A NULL configuration is also tolerated, so that an implicit instance
inherited by a proxy without the matching capability can keep its metadata
without applicable options.

The check uses the compression options stored in the filter configuration by
the preceding change, and only inspects the proxy that declared the
filter. When the algorithms are configured on the other proxy, that proxy
also gets an implicit filter for the direction, so an instance with a local
algorithm remains attached to perform the compression. This also prepares
the filter instances, where the same options are shared by the legacy
configurations and the instances.

4 weeks agoBUG/MINOR: flt-http-comp: Enable implicit filters independently per direction
Christopher Faulet [Tue, 29 Sep 2026 13:51:49 +0000 (15:51 +0200)]
BUG/MINOR: flt-http-comp: Enable implicit filters independently per direction

Since the compression filter was split into comp-req and comp-res,
check_implicit_http_comp_flt() still treated either explicit declaration as
sufficient to suppress all implicit compression filters. Otherwise it always
added both filters, regardless of the configured directions.

Thus, explicitly placing the request filter could prevent response
compression from being enabled, and conversely. An explicit declaration must
only fix that filter's position, not disable the other direction selected by
compression directives.

Track the two filters separately and append each missing filter when its
direction is enabled. Preserve existing explicit positions and continue to
require explicit placement of missing filters when another filter makes
their ordering ambiguous. Only the response compression filter is forbidden
before a cache filter; request compression does not affect the cached
response.

Do not require a local algorithm when adding an implicit filter. Direction
flags are combined across the frontend and backend, and the algorithm may be
supplied by the opposite proxy. The runtime checks still prevent any
compression when no algorithm is available for the selected direction.

This patch relies on following commits:

  * MINOR: flt-http-comp: Use compression options as filter config
  * MEDIUM: flt-http-comp: Enable compression directions from algorithms

The series should be backported to 3.4.

4 weeks agoMINOR: flt-http-comp: Use compression options as filter config
Christopher Faulet [Mon, 5 Oct 2026 19:51:04 +0000 (21:51 +0200)]
MINOR: flt-http-comp: Use compression options as filter config

All comp filter configurations left their ->conf pointer NULL: the request
and response compression filters resolved the proxy options at runtime,
through strm_fe(s)->comp and s->be->comp, so the pointer was never used by
the stream callbacks.

Now store the proxy's compression configuration (px->comp) in the filter
configurations, for the explicit "filter compression", "filter comp-req" and
"filter comp-res" declarations as well as for the filters implicitly
declared by check_implicit_http_comp_flt(). The compression options become
the actual configuration of these filters, like the decompression filters
already do with their direction.

No stream callback is changed: the runtime resolution through the frontend
and backend proxies is preserved, since the options of both proxies are
combined to compress a message, with a priority to the backend ones. But the
attach callback can now rely on FLT_CONF() to know whether a filter can be
useful, and the http-comp filter instances will set the same pointer.

The configuration remains owned by the proxy and released with it: the
compression filters have no deinit callback freeing ->conf.

4 weeks agoMEDIUM: flt-http-comp: Enable compression directions from algorithms
Christopher Faulet [Tue, 29 Sep 2026 13:51:49 +0000 (15:51 +0200)]
MEDIUM: flt-http-comp: Enable compression directions from algorithms

Compression options used to default to the response direction, even when
only a request algorithm was configured. Request compression then needed an
explicit direction or comp-req filter declaration. Also, the initial
direction depended on whether a filter declaration or a compression option
allocated the configuration first.

Start with no direction and let algo-req enable request compression and
algo/algo-res enable response compression. Add COMP_FL_DIR_FORCED to
remember an explicit compression direction, so that subsequent algorithm
directives do not re-enable a direction it excluded. This flag is also
inherited from defaults along with the other compression settings.

Only algorithm and direction directives enable compression implicitly.
Type, minimum-size and offload options configure its behavior but do not
enable a direction by themselves. Without an algorithm for that direction,
no compression can take place anyway.

This also avoids implicitly enabling response compression just because
request compression options were parsed before an explicit comp-req
filter. The legacy compression algo directive still enables responses.

4 weeks agoBUG/MINOR: flt-decomp: Fix the decompression filter keyword list name
Christopher Faulet [Fri, 2 Oct 2026 16:06:26 +0000 (18:06 +0200)]
BUG/MINOR: flt-decomp: Fix the decompression filter keyword list name

The decompression filter keyword list registered for the legacy "filter"
directive was named "COMP", like the HTTP compression one. Rename it to
"DECOMP" to match the filter it belongs to.

This only changes the keyword list label, not the accepted keywords.

4 weeks agoBUG/MINOR: spoe: Fix the message list splice with an empty list
Christopher Faulet [Wed, 7 Oct 2026 19:18:29 +0000 (21:18 +0200)]
BUG/MINOR: spoe: Fix the message list splice with an empty list

At the end of the parsing of a SPOE agent configuration file, the messages
of the file are moved to the agent message list with a manual splice.
However, this is only correct when the file defines at least one
"spoe-message" section. With an empty list, the manual splice will corrupt
the agent message list.

Replace the manual splice with LIST_SPLICE(), which does nothing with an
empty source list. This fixes the bug and this avoids to deal with list
pointers manually.

The regression was introduced by the commit 11610f3 ("MEDIUM: spoe: Parse
new "spoe-group" section in SPOE config file").

The patch must be backported to all supported versions.

4 weeks agoREGTESTS: log: check that a log-forward TCP session ends on client close
Yeonggi Kim [Tue, 6 Oct 2026 09:12:47 +0000 (18:12 +0900)]
REGTESTS: log: check that a log-forward TCP session ends on client close

This test covers the bug fixed by the previous commit:

    BUG/MEDIUM: log: do not ignore the peer's shutdown in the syslog applet

A log-forward section without "timeout client" accepts a single
connection at a time ("maxconn 1"). A first client sends a message and
only closes once the message was forwarded, so that its shutdown is
received alone, on an empty input buffer. A second client then sends
another message, which can only be forwarded once the first session is
gone. On an affected, unpatched build, the first session never ends and
the test fails on its timeout. It passes on 3.2, which is not affected.

This must be backported wherever the fix above is backported.

4 weeks agoBUG/MEDIUM: log: do not ignore the peer's shutdown in the syslog applet
Yeonggi Kim [Tue, 6 Oct 2026 09:12:46 +0000 (18:12 +0900)]
BUG/MEDIUM: log: do not ignore the peer's shutdown in the syslog applet

Since 5da704b55 ("MEDIUM: log: Update the log applet to use its own
buffers"), syslog_io_handler() leaves early with applet_need_more_data()
when its input buffer is empty:

inbuf = applet_get_inbuf(appctx);
if (inbuf == NULL || !applet_input_data(appctx)) {
goto missing_data;
}

This skips applet_getchar(), which is where the handler notices the
client's shutdown: on an empty buffer it returns -1 once SE_FL_SHW is
set, and the session is closed through cli_abort. Before that commit,
co_getchar() was reached the same way on an empty buffer. Now, when the
shutdown is received alone, after the previous messages were consumed,
the TCP syslog session stays open. A log-forward section does not
inherit "timeout client" from the defaults section, so unless one is set
in the section itself, nothing ever closes the session.

Reproducer:

    global
        stats socket /tmp/haproxy.sock level admin

    log-forward relay
        bind 127.0.0.1:1514
        log 127.0.0.1:1515 local0

Three clients which each send one message to 127.0.0.1:1514 and close
the connection once it was forwarded leave three sessions in "show sess"
(none with this patch). A soft-stop then waits for "hard-stop-after",
and forever without it. This also happens when a "ring" forwards to a
log-forward section of the same process: on soft-stop the ring's
forwarder closes its connection, the session on the log-forward side
stays, so after a reload the old process is only stopped by
"hard-stop-after".

Only a missing input buffer (not allocated yet) must still make the
handler leave early. An empty one now goes through applet_getchar(),
which returns 0 (wait for more data, as before) or -1 once SE_FL_SHW is
set. The dns and peers applets already work this way: they only test
applet_get_inbuf() for NULL and rely on the getters to report the
shutdown. As before 5da704b55, a closed session is accounted in
cli_aborts, as already done when the client closes in the middle of a
message.

This must be backported as far as 3.3.

4 weeks agoREGTESTS: cli: test a command line of exactly tune.bufsize bytes
Yeonggi Kim [Tue, 6 Oct 2026 11:29:11 +0000 (20:29 +0900)]
REGTESTS: cli: test a command line of exactly tune.bufsize bytes

This test covers the bug fixed by the previous commit:

    BUG/MEDIUM: cli: fix the last command detection on a full command line

With "tune.bufsize 1024", two clients each send a line of exactly 1024
bytes: one command padded with spaces, then two pipelined commands. The
commands change the global rate limits and produce no output, so
"expect_close" only has to skip the empty lines sent after each command
before the connection is closed. On an unpatched build, it fails on the
first "Unknown command" error. With DEBUG_STRICT=2, haproxy dies
instead, and the test times out on the following "show info", which
checks that each command was executed.

This must be backported wherever the fix above is backported.

4 weeks agoBUG/MEDIUM: cli: fix the last command detection on a full command line
Yeonggi Kim [Tue, 6 Oct 2026 11:29:10 +0000 (20:29 +0900)]
BUG/MEDIUM: cli: fix the last command detection on a full command line

Since 20ec1de214 ("MAJOR: cli: Refacor parsing and execution of
pipelined commands"), a whole command line is copied into a chunk before
its commands are executed, and the last command is detected by comparing
its end with the tail of this chunk:

end_of_cmdline = (... : b_tail(appctx->cli_ctx.cmdline)-1);
...
if (end == end_of_cmdline) {
appctx->st1 |= APPCTX_CLI_ST1_LASTCMD;

The chunk is a trash chunk of tune.bufsize bytes, and b_getdelim() fills
it up to its last byte when the line, including its final LF, is exactly
tune.bufsize bytes long. b_tail() then wraps to the beginning of the
area, and the last command is never detected. Once it is executed, the
words of the line, separated by NUL bytes by the first parsing, are
parsed again one by one as if more commands were pending, and b_del()
removes more bytes than the chunk holds, which makes b_data() underflow.
This never ends: the CLI sends "Unknown command" errors, keeping a
thread busy for as long as the client reads them. A build with
DEBUG_STRICT=2 dies instead on the BUG_ON_HOT() in b_del(). With one
byte less, the line is processed normally, and with one byte more, it is
rejected as too big. The same happens when several commands separated
by semicolons fill the chunk, or with a line sent through the master CLI
which becomes exactly tune.bufsize bytes long once the master added its
prefixes (e.g. "expert-mode on -;").

Reproducer, with "tune.bufsize 1024" and a "stats socket":

    printf 'show version%1011s\n' '' | socat - /tmp/haproxy.sock

The command line chunk is allocated empty by alloc_trash_chunk(), and is
only filled by cli_parse_cmdline(), from its beginning, before any of
its commands is processed. cli_process_cmdline() then only removes data
from its head. So the data never wrap, and the end of the command line
is now computed from its head and the amount of data, which also works
when the chunk is full.

This must be backported as far as 3.2. On 3.3 and 3.2, the end of the
command line is tested with "end+1 == b_tail(appctx->cli_ctx.cmdline)",
which must become "end+1 == orig + b_data(appctx->cli_ctx.cmdline)".

4 weeks agoMEDIUM: spoe: add the "on-switching-rules" event
Benedict Weis [Fri, 9 Oct 2026 09:18:54 +0000 (09:18 +0000)]
MEDIUM: spoe: add the "on-switching-rules" event

Add a new SPOE event, "on-switching-rules", triggered on the frontend side
after the evaluation of "tcp-request content" rules but before the
"use_backend" switching rules. This lets a SPOA agent inspect the request
and influence backend selection (e.g. set a variable that a "use_backend"
rule then reads). The main use case is SNI-based routing in TCP mode.

The event is hooked on the AN_REQ_SWITCHING_RULES analyzer via the filter
pre-analyze callback, so it runs after tcp_inspect_request() (which
evaluates "tcp-request content" rules and honors "tcp-request
inspect-delay") and before process_switching_rules(). Its sample-fetch
validity checkpoint is SMP_VAL_FE_SET_BCK, matching the point at which
use_backend rules are evaluated. Like the other frontend events it is only
accepted for SPOE filters declared in a frontend or listen section.

Note that, in TCP mode, request data (such as req.ssl_sni) is only
guaranteed to be available at this event if a "tcp-request content" rule
combined with a "tcp-request inspect-delay" actually waits for it; with no
such rule the analyzers run at connection time and the event fires with no
request data buffered yet.

The event is documented in doc/SPOE.txt, both in the "event" directive list
and in the "Events & Messages" section. A functional reg-test
(reg-tests/spoe/switching_rules.vtc) drives it end to end with a minimal,
stdlib-only SPOA mock (spoa_mock.py), avoiding external dependencies: the
agent populates sess.spoe.backend_name from a NOTIFY and a "use_backend"
rule routes traffic accordingly.

This resolves GitHub issue #3220.

This is a new feature, no backport needed.

Co-authored-by: Alexander Stephan <alexander.stephan@sap.com>
4 weeks agoCLEANUP: cfgparse: remove the unused common_kw_list[] definitions
Willy Tarreau [Thu, 8 Oct 2026 17:03:58 +0000 (19:03 +0200)]
CLEANUP: cfgparse: remove the unused common_kw_list[] definitions

These were the old (and incomplete) lists of section names for use
with cfg_parse_global() and cfg_parse_listen(), that were passed to
cfg_find_best_match(). They're no longer needed so let's get rid of
them.

4 weeks agoMINOR: cfgparse: always suggest the section names on an unknown keyword
Willy Tarreau [Thu, 8 Oct 2026 17:31:42 +0000 (19:31 +0200)]
MINOR: cfgparse: always suggest the section names on an unknown keyword

When an unknown keyword is met in a section, cfg_find_best_match() looks
for the closest match among the keywords registered for that section,
plus an optional list of extra words passed by the caller, which
cfg_parse_global() and cfg_parse_listen() were using to pass a list of
a few known hard-coded section names. But this resulted several times
in some names not being matched (e.g. "resolvers" which was later added
but only for "global"), or others still not being known, e.g.

  $ printf 'global\n\tcrt-stor\n' | ./haproxy -c -f /dev/stdin
  [ALERT] : parsing [/dev/stdin:2] : unknown keyword 'crt-stor' in
    'global' section; did you mean 'crt-store' maybe ?

Let's have the function always consider all known section names since
they are always permitted, that is much more effective and will no
longer require to hard-code certain combinations. This completely drops
the extra argument that's no longer used.

4 weeks agoCLEANUP: cfgparse-listen: simplify the second half of cfg_parse_listen()
Willy Tarreau [Thu, 8 Oct 2026 16:48:46 +0000 (18:48 +0200)]
CLEANUP: cfgparse-listen: simplify the second half of cfg_parse_listen()

All the proxy keywords are now registered in cfg_kws, so the function
only has to create the sections ("listen", "frontend", "backend" and
"defaults") and to look the keyword up in the registered lists. The
historical indented block that was dealing with known keywords is no
longer necessary and it's the right moment to reindent the few tens
of remaining lines.

4 weeks agoCLEANUP: cfgparse-listen: move "option" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 15:58:18 +0000 (17:58 +0200)]
CLEANUP: cfgparse-listen: move "option" to its own parser

This was by far the largest block of cfg_parse_listen(), about 470
lines. It is now registered in cfg_kws and parsed by
proxy_parse_option(), making the keyword discoverable via "-dKcfg". The
keyword modifier is taken from cfg_curr_kwm since the keyword parsers
don't receive it.

The whole block of non-standard options was a bit rearranged by
dependencies or behaviors so as to factor some code, mode checks and
flags setting.

4 weeks agoMINOR: log: add proxy_logformat_origin() to detect a proxy's log-format type
Willy Tarreau [Thu, 8 Oct 2026 16:42:16 +0000 (18:42 +0200)]
MINOR: log: add proxy_logformat_origin() to detect a proxy's log-format type

This new function will check the proxy's logformat.str and return return
an option name (e.g. "option httplog") or a directive ("log-format") that
will permit error messages to report where a log-format had been set.

4 weeks agoCLEANUP: cfgparse-listen: move "bind" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 15:53:44 +0000 (17:53 +0200)]
CLEANUP: cfgparse-listen: move "bind" to its own parser

The keyword is now registered in cfg_kws and parsed by
proxy_parse_bind(), making it discoverable via "-dKcfg".

The now unused bind_conf variable and alloc_error label were removed
from cfg_parse_listen() to avoid warnings.

4 weeks agoCLENAUP: cfgparse-listen: move the "server" keywords to their own parser
Willy Tarreau [Thu, 8 Oct 2026 15:49:55 +0000 (17:49 +0200)]
CLENAUP: cfgparse-listen: move the "server" keywords to their own parser

"server", "default-server" and "server-template" only differ by the
flags they pass to parse_server(), so they were moved together to a new
proxy_parse_server() function registered in cfg_kws, making them
discoverable via "-dKcfg". parse_server() emits its own messages, so the
new parser only has to turn a fatal error into a negative return value.

4 weeks agoCLEANUP: cfgparse-listen: move "stats" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 15:31:05 +0000 (17:31 +0200)]
CLEANUP: cfgparse-listen: move "stats" to its own parser

The keyword is now registered in cfg_kws and parsed by
proxy_parse_stats(), making it discoverable via "-dKcfg". It needs the
default proxy and takes it via the <defpx> argument.

Like for previous http-request rules, the "http-request stats" action
will now return only the first failure and not all of them at once.

Note that <acl_cond> was removed from cfg_parse_listen() to avoid a
build warning since it was the last part using it, we're getting
closer to the end!

4 weeks agoCLENAUP: cfgparse-listen: move "email-alert" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 15:21:29 +0000 (17:21 +0200)]
CLENAUP: cfgparse-listen: move "email-alert" to its own parser

The keyword is now registered in cfg_kws and parsed by
proxy_parse_email_alert(), making it discoverable via "-dKcfg".

The "from", "mailers", "myhostname" and "to" options only differ by the
field they assign, so the field is now selected first and the rest of
the processing, including the check for a missing value, is shared.
~25% code removed in the operation.