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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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:
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.
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.
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.
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:
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":
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)".
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>
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.
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.
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.
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.
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.
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.
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!
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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:
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.
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.
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().
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.
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.
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).
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".
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".
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.
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.
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.
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.
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)
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.
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.
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.
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.
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).
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".
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.
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.
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).
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.
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.
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".
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.
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:
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.
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.
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:
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.