]> git.kaiwu.me - haproxy.git/log
haproxy.git
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.

4 weeks agoCLEANUP: cfgparse-listen: move "cookie" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 14:00:17 +0000 (16:00 +0200)]
CLEANUP: cfgparse-listen: move "cookie" to its own parser

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

The "maxidle" and "maxlife" options were essentially re-merged since
they differed by only the struct member receiving the parsed value.
The code was refactored a little bit since it never happened over the
years when keywords were added, but overall nothing fancy.

There's one minor change though, that's imposed by keyword parsers:
previously it was possible to report up to 3 alerts if multiple args
were invalid, but now it's no longer possible since keyword parsers
can only report one error, so only the first one is reported. No big
deal.

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

The stick rules are forbidden in a defaults section. They were moved
to a new proxy_parse_stick() function registered in cfg_kws, making
them discoverable via "-dKcfg".

The code is mostly the same, with error paths reworked to avoid chains of
free()+return calls in error paths.

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

The directive is forbidden in a defaults section. It's now moved to a
new proxy_parse_stick_table() function registered in cfg_kws, making
it discoverable via "-dKcfg". Nothing else really changed.

4 weeks agoCLEANUP: cfgparse-listen: move "redirect" to its own parser
Willy Tarreau [Thu, 8 Oct 2026 13:51:21 +0000 (15:51 +0200)]
CLEANUP: cfgparse-listen: move "redirect" to its own parser

The keyword is now registered in cfg_kws and parsed by
proxy_parse_redirect(), making it discoverable via "-dKcfg". It
couldn't be merged with the http-request rules because the rules
are of type redirect_rule while the other ones were act_rule.

4 weeks agoCLEANUP: cfgparse-listen: move the HTTP rule sets to their own parser
Willy Tarreau [Thu, 8 Oct 2026 13:45:14 +0000 (15:45 +0200)]
CLEANUP: cfgparse-listen: move the HTTP rule sets to their own parser

"http-request", "http-response" and "http-after-response" were handled
by three same blocks which only differed by the rule list, the parsing
function and the validity masks. They were moved to a new function
proxy_parse_http_rules(), making the keywords discoverable via "-dKcfg".

Note that a failure to parse the action now returns a fatal error
instead of immediately aborting the config parsing, so that some of
the remaining lines are still checked.

4 weeks agoCLEANUP: cfgparse-listen: add parser for server/backend sekection keywords
Willy Tarreau [Thu, 8 Oct 2026 09:33:00 +0000 (11:33 +0200)]
CLEANUP: cfgparse-listen: add parser for server/backend sekection keywords

Let's group "default_backend", "use_backend" and "use-server" into their
own parser proxy_parse_use_backend() registered in cfg_kws, making them
discoverable via "-dKcfg". Not much changes.

4 weeks agoCLEANUP: cfgparse-listen: move "source" "usesrc" and "interface" to their own parser
Willy Tarreau [Thu, 8 Oct 2026 09:20:49 +0000 (11:20 +0200)]
CLEANUP: cfgparse-listen: move "source" "usesrc" and "interface" to their own parser

The keywords are now registered in cfg_kws and parsed by
proxy_parse_source(), making them discoverable via "-dKcfg". The loop
was slightly reorganized. We cannot really use the BUG_ON() trap here
for unregistered keywords due to the loop that tries to consume as many
sub-keywords as possible (since "usesrc" and "interface" are in fact
options of "source" parsed as if they were first-level ones).

4 weeks agoCLEANUP: cfgparse-listen: move the load-balancing keywords to their own parser
Willy Tarreau [Thu, 8 Oct 2026 09:19:08 +0000 (11:19 +0200)]
CLEANUP: cfgparse-listen: move the load-balancing keywords to their own parser

"balance", "hash-type" and "hash-balance-factor" are used to configure
the load balancing and require the backend capability, so they were moved
together to a new proxy_parse_balance() function registered in cfg_kws,
making them discoverable via "-dKcfg". As usual, backend_parse_balance()
now needs to take care of releasing the error message.

4 weeks agoCLEANUP: cfgparse-listen: add parser for "log", "log-tag" and "unique-id-header"
Willy Tarreau [Thu, 8 Oct 2026 09:14:48 +0000 (11:14 +0200)]
CLEANUP: cfgparse-listen: add parser for "log", "log-tag" and "unique-id-header"

The three keywords were moved to a new proxy_parse_log_opts() function
registered in cfg_kws, making them discoverable via "-dKcfg". The "log"
keyword consults cfg_curr_kwm to detect "no log", and the header name is
now allocated before the previous one is released. Arguably we could have
moved unique-id-header in its own function, or move them all to another
existing function, this doesn't change much. They were taken together
as they were grouped in the original code block.

4 weeks agoCLEANUP: cfgparse-listen: move the log format keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 16:52:10 +0000 (18:52 +0200)]
CLEANUP: cfgparse-listen: move the log format keywords to their own parser

"log-format", "log-format-sd", "error-log-format" and "unique-id-format"
all assign a log format expression to the proxy and were handled almost
the same way, so they were moved together to proxy_parse_logformat() and
listed in cfg_kws, making them discoverable via "-dKcfg".

All common parts (arg check, allocation, warning about backends being
ignored) are now shared and performed first. The result of strdup() of
the file name is now checked. The code is cleaner and smaller:

   90 insertions(+), 124 deletions(-)

4 weeks agoCLEANUP: cfgparse-listen: move "capture" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 16:46:49 +0000 (18:46 +0200)]
CLEANUP: cfgparse-listen: move "capture" to its own parser

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

Note that the code was rearranged a bit so that the checks for defaults
section that were common between the 3 variants are now made only once
at the beginning of the function. A little bit more factoring was done
but overall nothing fancy.

4 weeks agoCLEANUP: cfgparse-listen: move the persistence keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 16:28:27 +0000 (18:28 +0200)]
CLEANUP: cfgparse-listen: move the persistence keywords to their own parser

"persist", "force-persist" and "ignore-persist" were moved to a new
proxy_parse_persist() function registered in cfg_kws, making them
discoverable via "-dKcfg". Just a few cosmetic changes again.

4 weeks agoCLEANUP: cfgparse-listen: move the monitor keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 16:25:52 +0000 (18:25 +0200)]
CLEANUP: cfgparse-listen: move the monitor keywords to their own parser

"monitor-uri" and "monitor" both configure the monitoring requests and
require the frontend capability, so they were moved together to a new
proxy_parse_monitor() function registered in cfg_kws, making them
discoverable via "-dKcfg".

The "monitor" keyword needs to release the error message reported by
build_acl_cond().

4 weeks agoCLEANUP: cfgparse-listen: move "acl" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 16:16:50 +0000 (18:16 +0200)]
CLEANUP: cfgparse-listen: move "acl" to its own parser

The keyword is now registered in cfg_kws and parsed by proxy_parse_acl(),
making it discoverable via "-dKcfg". Care was taken to release the
errptr returned by parse_acl().

The code could also have been moved to acl.c but it appeared strange there
because acl.c doesn't really deal with first-level keywords and doesn't
care about proxies, and here we're in a proxy section, so aftre all,
cfgparse-listen seems to remain the best place for the function.

4 weeks agoCLEANUP: cfgparse-listen: move "id" and "description" to their own parser
Willy Tarreau [Wed, 7 Oct 2026 16:12:02 +0000 (18:12 +0200)]
CLEANUP: cfgparse-listen: move "id" and "description" to their own parser

Both keywords set the name and desc of the proxy and are both
forbidden in a defaults section, so they were moved together to a new
proxy_parse_id_desc() function registered in cfg_kws, making them
discoverable via "-dKcfg". Nothing really special here.

4 weeks agoCLEANUP: cfgparse-listen: move "mode" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 16:07:26 +0000 (18:07 +0200)]
CLEANUP: cfgparse-listen: move "mode" to its own parser

The keyword is now registered in cfg_kws and parsed by proxy_parse_mode(),
making it discoverable via "-dKcfg". The core remains essentially the
same.

4 weeks agoCLEANUP: cfgparse-listen: move the backend one-arg keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 16:05:59 +0000 (18:05 +0200)]
CLEANUP: cfgparse-listen: move the backend one-arg keywords to their own parser

"retries", "http-reuse", "http-send-name-header", "dynamic-cookie-key",
"load-server-state-from-file" and "server-state-file-name" all require
the backend capability and take a single argument, so they were moved
together to a new proxy_parse_be_opts() function registered in cfg_kws,
making them discoverable via "-dKcfg". The code was slightly cleaned up
while moved, but nothing sensitive (removed some flag dance which already
caused bugs in the past, etc).

4 weeks agoCLEANUP: cfgparse-listen: move the connection limits to thier own parser
Willy Tarreau [Wed, 7 Oct 2026 16:00:44 +0000 (18:00 +0200)]
CLEANUP: cfgparse-listen: move the connection limits to thier own parser

"maxconn", "backlog", "fullconn" and "max-session-srv-conns" all take a
single integer argument and only differ by the capability they require
and the field they assign, so they were moved together to a new
proxy_parse_conn_limits() function registered in cfg_kws, making them
discoverable via "-dKcfg".

4 weeks agoCLEANUP: cfgparse-listen: move "disabled" and "enabled" to their own parser
Willy Tarreau [Wed, 7 Oct 2026 15:59:26 +0000 (17:59 +0200)]
CLEANUP: cfgparse-listen: move "disabled" and "enabled" to their own parser

Both keywords only set or clear PR_FL_DISABLED, so they were moved
together to a new proxy_parse_enabled() function registered in cfg_kws,
making them discoverable via "-dKcfg".

4 weeks agoMEDIUM: cfgparse-listen: move removed keywords' help messages into an array
Willy Tarreau [Wed, 7 Oct 2026 15:50:23 +0000 (17:50 +0200)]
MEDIUM: cfgparse-listen: move removed keywords' help messages into an array

The 29 remaining removed keywords are almost all handled the same way:
  - the version where the keyword was removed is reported (except for
    "bind-process")
  - a help message is almost always provided

Instead of writing lots of code that is not always consistent, better
place these keywords in an array with their version and help messages,
leaving NULL for the missing info. For "bind-process", the relevant
version was updated (2.7).

The proxy_parse_removed_kw() parser was extended to consult this table,
and the keywords only had to be added to the standard parser, pointing
to this function as their parser. This way, the same output messages as
before are emitted with much less code and even less risks of errors.

The list of converted keywords is:

  appsession, bind-process, block, cliexp, grace, redisp, redispatch,
  reqadd, reqallow, reqdel, reqdeny, reqiallow, reqidel, reqideny,
  reqipass, reqirep, reqitarpit, reqpass, reqrep, reqtarpit, rspadd,
  rspdel, rspdeny, rspidel, rspideny, rspirep, rsprep, srvexp,
  transparent,

4 weeks agoMINOR: cfgparse-listen: introduce proxy_parse_removed_kw() and use it
Willy Tarreau [Wed, 7 Oct 2026 15:46:51 +0000 (17:46 +0200)]
MINOR: cfgparse-listen: introduce proxy_parse_removed_kw() and use it

A significant number of cfgparse_listen()'s keywords are handled
specifically because they're obsolete and continue to deliver a detailed
indication about what to use instead. The most complicated ones are
"dispatch" and "monitor-net" which even suggest the exact line to use
in order to replace them.

Let's introduce proxy_parse_removed_kw() to handle such keywords and
their excentricities. The function is just a regular keyword parser,
so that the keywords it handles are now in the standard array and point
to it.

For now we only implement "monitor-net" and "dispatch". The output
messages remain exactly the same.

4 weeks agoCLEANUP: cfgparse-global: simplify cfg_parse_global()
Willy Tarreau [Wed, 7 Oct 2026 15:34:57 +0000 (17:34 +0200)]
CLEANUP: cfgparse-global: simplify cfg_parse_global()

Now that all the keywords are registered in cfg_kws, the function only
has to deal with the "global" section name itself and to lookup the
keyword in the registered lists. The "discovery_kw" label and the old
"else" block are no longer used and were removed. The function which
used to be ~1000 lines in master is now only ~60.

4 weeks agoCLEANUP: cfgparse-global: ask not to add new keywords in common_kw_list[]
Willy Tarreau [Wed, 7 Oct 2026 15:29:21 +0000 (17:29 +0200)]
CLEANUP: cfgparse-global: ask not to add new keywords in common_kw_list[]

Better discourage new keywords from being added again to the remaining
list, which currently still contains a few section names that are not
discovered otherwise. We just add a comment.

4 weeks agoCLEANUP: cfgparse-global: move "anonkey" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 15:25:15 +0000 (17:25 +0200)]
CLEANUP: cfgparse-global: move "anonkey" to its own parser

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

This was the last keyword parsed by strcmp() in cfg_parse_global().

4 weeks agoCLEANUP: cfgparse-global: move "cpu-map" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 15:18:43 +0000 (17:18 +0200)]
CLEANUP: cfgparse-global: move "cpu-map" to its own parser

The keyword is now registered in cfg_kws and parsed by
cfg_parse_global_cpu_map(), making it discoverable via "-dKcfg". The
code is mostly unchanged, except that the errmsg returned but the
called function now has to be freed here (it was done in the
cfgparse_global loop before)

4 weeks agoCLEANUP: cfgparse-global: move the spread-checks keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 15:13:35 +0000 (17:13 +0200)]
CLEANUP: cfgparse-global: move the spread-checks keywords to their own parser

"spread-checks" and "max-spread-checks" both control how the health
checks are spread over time, so they were moved together to a new
cfg_parse_global_spread_checks() function and registered in cfg_kws so
as to become discoverable via "-dKcfg".

The "already specified" message now uses a warning.

4 weeks agoCLEANUP: cfgparse-global: move the state file keywords to their own parser
Willy Tarreau [Wed, 7 Oct 2026 15:10:19 +0000 (17:10 +0200)]
CLEANUP: cfgparse-global: move the state file keywords to their own parser

"server-state-base", "server-state-file" and "stats-file" all store a
path which is used to preload some state at boot, and were handled
the same way, so let's move them to cfg_parse_global_state_files()
so as to become discoverable via "-dKcfg".

Like for a few previous commits, the result of the strdup() is now
checked, and the "already specified" warning issues a warning.

4 weeks agoCLEANUP: cfgparse-global: move the log keywords to a single parser
Willy Tarreau [Wed, 7 Oct 2026 14:48:59 +0000 (16:48 +0200)]
CLEANUP: cfgparse-global: move the log keywords to a single parser

"log", "log-send-hostname" and "log-tag" are all related to the logging
setup, so they were moved together to a new cfg_parse_global_log_opts()
function and registered in cfg_kws so as to become discoverable via
"-dKcfg". The "log" keyword uses cfg_curr_kwm to detect "no log".

Like for previous commits, the result of the strdup() of the hostname
is now checked, and the duplicate hostname only emits a warning.

4 weeks agoCLEANUP: cfgparse-global: move "unix-bind" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:44:49 +0000 (16:44 +0200)]
CLEANUP: cfgparse-global: move "unix-bind" to its own parser

The "unix-bind" keyword even had its own args loop in cfg_parse_global().
It's now registered in cfg_kws and parsed by cfg_parse_global_unix_bind(),
making it discoverable via "-dKcfg". The code was slightly restructured,
particularly the long chain of "if (...) { ...; continue; }" was simplified
a bit.

Like in a few previous patches, the duplicate test for "prefix" now just
emits a warning, and the result of strdup() is tested.

4 weeks agoCLEANUP: cfgparse-global: move "node" and "description" to their own parser
Willy Tarreau [Wed, 7 Oct 2026 14:41:09 +0000 (16:41 +0200)]
CLEANUP: cfgparse-global: move "node" and "description" to their own parser

Both keywords give an name/desc to the current node, so they were moved
together to a new cfg_parse_global_node_desc() function and registered in
cfg_kws so as to become discoverable via "-dKcfg".

While doing this, a test on strdup(node) result was added so as to fail
cleanly on memory allocation failure (this remains i nthe scope of a
cleanup since in practice it never happens).

4 weeks agoCLEANUP: cfgparse-global: move "ssl-server-verify" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:35:56 +0000 (16:35 +0200)]
CLEANUP: cfgparse-global: move "ssl-server-verify" to its own parser

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

4 weeks agoCLEANUP: cfgparse-global: move the compression limits to a single parser
Willy Tarreau [Wed, 7 Oct 2026 14:35:35 +0000 (16:35 +0200)]
CLEANUP: cfgparse-global: move the compression limits to a single parser

"maxcomprate", "maxzlibmem" and "maxcompcpuusage" all limit the resources
which may be dedicated to the compression, so they were moved together to
a new cfg_parse_global_comp_limits() function and registered in cfg_kws
so as to become discoverable via "-dKcfg".

4 weeks agoCLEANUP: cfgparse-global: move the process-wide limits to a single parser
Willy Tarreau [Wed, 7 Oct 2026 14:35:28 +0000 (16:35 +0200)]
CLEANUP: cfgparse-global: move the process-wide limits to a single parser

"maxconn", "maxconnrate", "maxpipes", "maxsessrate", "maxsslrate",
"fd-hard-limit" and "ulimit-n" all take a single integer argument, may
only be specified once and simply assign a field in the global struct,
so they were moved together to a new cfg_parse_global_limits() function
and registered in cfg_kws so as to become discoverable via "-dKcfg".

The arguments are now checked before the value is assigned rather than
after. The two non-fatal alerts ("already specified" and "maxconn too high")
are emitted directly via ha_warning() / ha_alert(). (Same as for previous
patch, "already specified" turned to a warning) since more appropriate.

4 weeks agoCLEANUP: cfgparse-global: move "cluster-secret" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:28:20 +0000 (16:28 +0200)]
CLEANUP: cfgparse-global: move "cluster-secret" to its own parser

The keyword is now registered in cfg_kws and parsed by
cfg_parse_global_cluster_secret(), making it discoverable via "-dKcfg".
Note that using ha_alert() to warn about a repetition did not make much
sense since it didn't declare a fatal error, and since it doesn't prevent
from starting, it's better turned to a warning so that's what was done
here.

4 weeks agoCLEANUP: cfgparse-global: move "external-check" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:24:32 +0000 (16:24 +0200)]
CLEANUP: cfgparse-global: move "external-check" to its own parser

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

4 weeks agoCLEANUP: cfgparse-global: move "uid", "gid", "user" and "group" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:22:30 +0000 (16:22 +0200)]
CLEANUP: cfgparse-global: move "uid", "gid", "user" and "group" to its own parser

These 4 keywords all end up setting global.{uid,gid}, either from a
number or by resolving a system user or group name, so they were moved
together to a new cfg_parse_global_uid_gid() function and registered in
cfg_kws so as to become discoverable via "-dKcfg". The parser continues
to use ha_alert() / ha_warning() since keyword parsers can normally only
return a single message (e.g. "statically built libc" could be emitted
before the rest).

4 weeks agoCLEANUP: cfgparse-global: move "set-dumpable" to its own parser
Willy Tarreau [Wed, 7 Oct 2026 14:18:55 +0000 (16:18 +0200)]
CLEANUP: cfgparse-global: move "set-dumpable" to its own parser

The keyword is now registered in cfg_kws and parsed by
cfg_parse_global_set_dumpable(), so that it becomes discoverable via
"-dKcfg". It relies on cfg_curr_kwm to detect the "no" modifier.

4 weeks agoCLEANUP: cfgparse-global: move the argument-less boolean options to a parser
Willy Tarreau [Wed, 7 Oct 2026 14:15:59 +0000 (16:15 +0200)]
CLEANUP: cfgparse-global: move the argument-less boolean options to a parser

The following keywords were still parsed in cfg_parse_global() and were
not discoverable via "-dKcfg": "busy-polling", "nosplice", "nogetaddrinfo",
"noreuseport", "numa-cpu-mapping", "quick-exit", "strict-limits",
"limited-quic", "insecure-fork-wanted", "insecure-setuid-wanted" and
"h2-workaround-bogus-websocket-clients". They all take no argument and
just set or clear a boolean, so they were moved to a single new parser,
cfg_parse_global_bool_opts(), and registered in cfg_kws. The ones which
support the "no" modifier now consult cfg_curr_kwm for this.

The check for extra args is now factored and the "no" support is checked
at the upper level.

4 weeks agoMINOR: cfgparse: expose the keyword modifier to the keyword parsers
Willy Tarreau [Wed, 7 Oct 2026 14:06:44 +0000 (16:06 +0200)]
MINOR: cfgparse: expose the keyword modifier to the keyword parsers

Section parsers receive the keyword modifier ("no" or "default") found in
front of the keyword as their last argument, but the keyword parsers which
are registered in a cfg_kw_list only receive the arguments, the file name
and the line number. As a consequence, a keyword supporting negation such
as "busy-polling" cannot be moved out of a section parser.

Let's just expose the modifier of the line being parsed in a new global
variable, cfg_curr_kwm, which is set for each new line in readcfgfile()
and reset once the file is completely parsed. This way the few keyword
parsers which are interested in it may consult it, the same way they
already consult "file" and "line".

4 weeks agoCLEANUP: proxy: adapt the error message for removed "option transparent"
Willy Tarreau [Fri, 9 Oct 2026 07:26:46 +0000 (09:26 +0200)]
CLEANUP: proxy: adapt the error message for removed "option transparent"

The keyword was removed by commit be8159248f ("MAJOR: proxy: remove
support for "dispatch" and "transparent" proxy keywords"), but the
warning message for deprecated "option transparent" wasn't removed
now that the option may no longer be found.

4 weeks agoBUG/MINOR: cfgparse-listen: complete the list of options for the suggestions
Willy Tarreau [Tue, 15 Sep 2026 14:37:58 +0000 (16:37 +0200)]
BUG/MINOR: cfgparse-listen: complete the list of options for the suggestions

The options which need a dedicated parsing are not described in the
cfg_opts* arrays, so they are listed in common_options[] in order to be
proposed as a suggestion when one of them is mistyped. That list was
incomplete and was missing "accept-invalid-http-request",
"accept-invalid-http-response", "forceclose",
"http-restrict-req-hdr-names", "http-tunnel", "http_proxy", "httpslog"
and "use-small-buffers", and it spelled "originalto" as "original-to",
so none of these ever got suggested:

  $ printf 'backend b\noption originalt\n' | ./haproxy -c -f /dev/stdin
  [ALERT] : parsing [/dev/stdin:2] : unknown option 'originalt'.

In addition "spop-check" was listed unconditionally while the option
only exists when SPOE is enabled, so a build spoe would propose it
but fail to recognize it:

  [ALERT] : unknown option 'spop-chek'; did you mean 'spop-check' maybe ?
  ...
  [ALERT] : unknown option 'spop-check'; did you mean 'tcp-check' maybe ?

The list is now sorted, complete and conditional where needed, and it
received a comment saying that it must be kept in sync with the options.

Note that the existing cfg_opts* already handle a few special cases like
disabled or obsolete options. Maybe we could extend it to also support
special options that are just not parsed there and must appear somewhere
else. This could even further simplify the parser by preserving PR_CAP_*
to let the main parser handle incompatibilities.

This may be backported to all supported versions.

4 weeks agoBUG/MINOR: proxy: also look for the closest option among cfg_opts3
Willy Tarreau [Tue, 15 Sep 2026 14:37:58 +0000 (16:37 +0200)]
BUG/MINOR: proxy: also look for the closest option among cfg_opts3

proxy_find_best_option() scans cfg_opts[] and cfg_opts2[] for the option
which looks closest to a mistyped one. When cfg_opts3[] was introduced
in 3.2 with commit cc5a66212d ("MINOR: proxy: add proxy->options3"), it
was not added there, so such newly added options are not proposed as
candidates for mistakes (e.g. "assume-rfc6587-ntf" or "dont-parse-log").
Let's just fix this.

This may be backported to 3.2.

4 weeks agoBUG/MINOR: cfgparse-listen: properly reject extra args in a backend section
Willy Tarreau [Tue, 15 Sep 2026 14:37:58 +0000 (16:37 +0200)]
BUG/MINOR: cfgparse-listen: properly reject extra args in a backend section

A proxy section only accepts "<keyword> <name> [from <defaults>]", and
anything else was reported as an alert suggesting to use the "bind"
keyword, but only for sections with the frontend capability. For the
others the block was left silently, without any message and without
creating the proxy nor opening a bacend section! As such any keyword
so that either the next line was a section keyword and it would restart
from there, or any other proxy keyword was rejected with "'listen' or
'defaults' expected". The only really silent case was when the section
is the last one, as it's silently dropped:

  $ printf 'defaults\nmode http\nbackend b junk\n' | ./haproxy -c -f /dev/stdin
  $ echo 0
  0

This patch fixes the error report (both message and err_code) for
backends. It moves the too many args check after the fe/be distinction
so that we don't mix the two messages and we can always emit a relevant
message for both fronts and backs.

Since the only case where such a config would have been accepted was
when an empty and invalid backend definition was placed at the end
immediately after a defaults section, there's no risk to break any
working config so this can safely be backported.