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.
BUG/MINOR: cfgparse-listen: don't lose the errors reported while creating a proxy
When a proxy inherits from its defaults section, the return value of
proxy_ref_defaults() was assigned to err_code instead of being ORed
into it, wiping all the errors collected earlier for that line. As a
result an invalid character in a proxy's name was reported but the
process started anyway:
$ cat inv.cfg
defaults
mode http
frontend inv@lid
bind :8080
$ ./haproxy -c -f inv.cfg
[ALERT] : parsing [inv.cfg:3] : character '@' is not permitted in
'frontend' name 'inv@lid'.
$ echo 0
0
The same applies to the duplicate name and ambiguous defaults reference
diagnostics emitted before the proxy is created.
Configurations which were mistakenly accepted until will now be rejected,
though they were already emitting an alert. The issue was only introduced
in 3.4 with commit 87ea407cce ("MINOR: proxy: refactor proxy inheritance
of a defaults section"), so it's fairly unlikely that a bogus configuration
was introduced only there, thus a backport would be preferable, or maybe
only the err_code should be turned to ERR_WARN to remain tolerant for
existing versions.
BUG/MINOR: cfgparse-listen: always reject extra arguments to "persist"
The check for extra arguments was placed inside the "if" block which
parses "persist rdp-cookie(<name>)", so "persist rdp-cookie foo" was
silently accepted. Let's move it to the beginning of the keyword's
processing so that it covers all the cases.
This must NOT be backported since it makes the config parser stricter.
BUG/MINOR: cfgparse-global: reject extra arguments to numa-cpu-mapping and anonkey
Both keywords silently ignored anything passed after their expected
argument, contrary to all the other keywords of the section which
properly check for extra args. Let's just fix this.
This must NOT be backported since it makes the config parser stricter.
BUG/MINOR: cfgparse-listen: fix quoting of the condition conflict warning
The 10 places which report the message returned by warnif_cond_conflicts()
use "'%s.\n'" as a format, so the closing quote is emitted after the \n and
ends up alone at the beginning of the next line:
[WARNING] : parsing [x.cfg:4] : 'acl 'a' will never match ...
'
Let's just move the quote before the LF and even the trailing '.'.
BUG/MINOR: cfgparse-listen: fix the argument checked by "email-alert"
The "from", "mailers", "myhostname" and "to" options of "email-alert"
each check that a value was passed, but they test args[1], which is the
option's own name instead fo args[2]. As a consequence an empty args[2]
was accepted. The same copy-paste mistake made the "level" option error
handling report the option name instead of the wrong value.
BUG/MINOR: cfgparse-listen: release the previous description of the proxy
The proxy's "description" keyword did not release the previous value
before assigning the new one, so the first one was leaked when the
keyword was used twice, e.g. once in a defaults section and once in the
proxy. Note that the global section's "description" does it properly.
BUG/MINOR: cfgparse-global: add missing calloc() check for the global description
The global "description" keyword passes the result of calloc() directly to
snprintf() without checking it, resulting in a crash instead of an error
message if the allocation fails.
BUG/MINOR: cfgparse-global: add missing gotos to error label in max-spread-checks
The three error paths of the parse_time_err() call in the
"max-spread-checks" keyword only set the error code without leaving the
block, so that global.max_spread_checks was assigned an uninitialized
value right after the error was reported. No impact but clearly what
could report a build warning or a report from a static analyser. Let's
just add the missing gotos like everywhere else.
Willy Tarreau [Fri, 9 Oct 2026 06:52:44 +0000 (08:52 +0200)]
BUG/MAJOR: cfgparse: properly check userlist section when scanning keywords
In cfg_parse_users(), we scan all config keywords and only want to
evaluate those whose section is CFG_USERLIST. However, the section
is checked as a bit field instead of an equality, and CFG_USERLIST
is 3, which is CFG_GLOBAL|CFG_LISTEN, meaning that we can call a
wrong parser for keywords "user" or "group" when they are registered
in one of these sections. This is what randomly happens when the
global "group" keyword is refactored to have its own parser, depending
on build options the linker, it may randomly be found before or after
the userlist's and cause startup failures if the global section's
keyword parser is called from the userlist parser, such as in this
regtest that only fails with ASAN on the CI when the config only has
"group" set in a userlist section:
config : parsing [cfg:18] : cannot find group id for 'admins' (0:Success)
This was introduced in 3.4 with commit c17ed69bf3 ("MINOR: cfgparse:
Refactor "userlist" parser to print it in -dKall operation") so it must
be backported there.
If a config works it's OK, but this was still marked major because
there's simply no workaround for a failing binary where it only
depends on build options. It could happen for example if other
keywords are added via Lua or patches.
However, this introduces a regression when HTX buffer is smaller than
the copied block. In this case, QCS is flagged with QC_SF_DEM_FULL, but
early goto skip the increment on <htx_sent> total value.
This causes corrupted transfers with H3 POST larger than a bufsize
resulting in a larger payload. This is caused by duplicated data blocks
when copy resumes after hitting QC_SF_DEM_FULL condition.
Fix this by ensuring <htx_sent> is always updated before the early goto
path. Intent of the above fix is kept as well as <ret> still serves as a
comparison for QC_SF_DEM_FULL / EOM cases.
This must be backported along the above patch, planned up to 2.6 (after
a period of observation).
Lukas Tribus [Thu, 8 Oct 2026 17:16:21 +0000 (17:16 +0000)]
MINOR: doc: elaborate on diagnostic mode behavior
Commit 7b01a8dbdd("MINOR: global: define diagnostic mode of execution")
in 2.4-dev15 introduced diagnostic mode for config warnings.
It was initially designed to be ignored by "zero-warning" mode
(-dW) and documented accordingly.
Since commit ced4148401 ("BUG/MINOR: diag: always show the version
before dumping a diag warning") in 3.0-dev3 this changed, and
zero-warning mode combined with diagnostic messages stop haproxy from
starting. This appears to be a unintended consequence in ced4148401.
Since commit 7d63efa5f5 ("MINOR: errors: add ha_diag_notice() to
report diag-level notifications") there is a distinction between
what should not interact with zero-warning mode (ha_diag_notice()),
and the previous behavior with ha_diag_warning().
Explain the that some diagnostic message interact with zero-warning
and that diagnostic message will be backported, as such diagnostic
mode must only be used interactively.
MEDIUM: h3: do not close conn on unaligned non DATA frame
Until now, H3 parser rejected a non aligned frame, except for DATA. The
whole connection was closed with error H3_EXCESSIVE_LOAD, which can be
seen as a medium defect given that the client did not produce anything
invalid or even suspect.
In fact, most frame parsers are already able to support wrapping
content, as they deal with a struct buffer representation. This is also
the case for QPACK uni streams. The real limitation is for HEADERS frame
which rely on qpack_decode_fs() which can only work on aligned content.
This patch removes the connection closure on unaligned content. For H3
HEADERS frames, the buffer content is realigned by performing two
memmove() calls into a temporarily allocated alternative area. The
current parsing iteration will continue in this new aligned storage.
A future optimisation would be to support wrapping content in
qpack_decode_fs() to remove realign operation.
This can be considered as a bug, as previous state prevents to handle
valid H3 content. As no user complains about it, it may be better to
refrain from backporting the patch, unless there is some explicit demand
about it.
Note that original Rx buffer is not realigned, as this would mangle the
underlying MUX ncbuf storage. An alternative approach was implemented
first, performing the realign at the MUX level, however it was probably
less efficient as it required to copy the whole ncbuf. For the record,
this patch is attached here with its original message.
---
Subject: [PATCH] MEDIUM: quic/h3: support parsing of unaligned non DATA H3
frame
Until now, H3 parser was not able to parse a non DATA frame which wraps
in QCS Rx buffer. When this happened, the whole connection was closed
with error H3_EXCESSIVE_LOAD, which can be seen as a medium defect given
that the client did not produce anything invalid or even suspect.
This patch finally implements a method to deal with unaligned non DATA
H3 frames. When such frame is detected by H3 parser, rcv_buf callback is
interrupted. MUX will react to a null returned value and detect that the
buffer data is wrapping. In this case, the new function
qcs_realign_rx_data() is used to perform the realign operation using
b_slow_realign() helper. Callback rcv_buf is then reinvoked immediately.
This can be considered as a bug, as previous state prevents to handle
valid H3 content. As no user complains about it, it may be better to
refrain from backporting the patch, unless there is some explicit demand
about it.
---
src/h3.c | 8 ++------
src/mux_quic.c | 35 ++++++++++++++++++++++++++++++++---
2 files changed, 34 insertions(+), 9 deletions(-)
+/* Realign buffer content in <rxbuf>. This should be called when parsing is
+ * blocked due to wrapping content.
+ */
+static void qcs_realign_rx_data(struct qc_stream_rxbuf *rxbuf)
+{
+ struct ncbuf *ncb = &rxbuf->ncb;
+ struct buffer old;
+
+ /* Convert ncbuf to a full buffer type. This forces b_slow_realign()
+ * will copy every non contiguous blocks plus first ncb block header.
+ */
+ old = b_make(ncb_orig(ncb), ncb->size, ncb->head, ncb->size);
+ b_slow_realign(&old, trash.area, 0);
+
+ /* ncb area now realigned - ensure head is resetted as well. */
+ ncb->head = 0;
+}
+
/* Transfer data into <qcs> stream <rxbuf> current Rx buffer from its directly
* following buffer. This is useful when parsing was interrupted due to partial
* data. If following buffer does not exists, nothing is done.
@@ -1508,9 +1526,20 @@ static int qcc_decode_qcs(struct qcc *qcc, struct qcs *qcs)
* at a Rx buffer boundary. Try to realign data if possible and
* restart decoding.
*/
- if (!ret && rxbuf && !(qcs->flags & QC_SF_DEM_FULL) &&
- qcs->rx.offset + ncb_data(&rxbuf->ncb, 0) == rxbuf->off_end) {
- if (!qcs_transfer_rx_data(qcs, rxbuf)) {
+ if (!ret && rxbuf && !(qcs->flags & QC_SF_DEM_FULL)) {
+ int need_restart = 0;
+
+ if (ncb_head(&rxbuf->ncb) + ncb_data(&rxbuf->ncb, 0) > ncb_wrap(&rxbuf->ncb)) {
+ qcs_realign_rx_data(rxbuf);
+ need_restart = 1;
+ }
+
+ if (qcs->rx.offset + ncb_data(&rxbuf->ncb, 0) == rxbuf->off_end) {
+ if (!qcs_transfer_rx_data(qcs, rxbuf))
+ need_restart = 1;
+ }
+
+ if (need_restart) {
TRACE_DEVEL("restart parsing after data realignment", QMUX_EV_QCS_RECV, qcc->conn, qcs);
goto restart;
}
--
2.56.0
BUG/MEDIUM: h3: fix end-of-input report on DATA wrapping
A received H3 DATA frame may wrap in the buffer. Function
h3_data_to_htx() will parse it in two pass in this case, calling
htx_add_data() twice.
A double bug was present in case of such parsing as second call of
htx_add_data() return value was immediately added in <htx_sent>. This
could cause first check to activate QC_SF_DEM_FULL to be ignored. This
is a minor issue, as MUX would immediately reinvoke h3_rcv_buf(), with
the flag set this time.
The second bug is more important : comparison for HTX EOM reporting was
invalid in case of a two pass parsing. Most of the time, this result in
EOM not reported, causing the stream layer to report a stream
interruption with state CL--.
Fix this by using an intermediary return value for the second
htx_add_data() invokation. This value is only added to <htx_sent> once
necessary checks are performed. It is important to keep h3_data_to_htx()
to still return the total value of HTX added data to avoid parser desync
issue.
This must be backported up to 2.6, after a period of observation.
BUG/MINOR: ssl: Fix potential NULL-deref in "dump ssl cert" with AWS-LC
In some corner cases we could have a public certificate with no private
key stored in a crt-store. In such a case, when trying to dump the
contents of the store entry via the "dump ssl cert" CLI command, the
'PEM_write_bio_PrivateKey' was always called without checking for a NULL
pointer.
This poses no problems with OpenSSL libraries which already check the
pointer but on some other libs, AWS-LC for instance, the NULL pointer is
dereferenced without check.
The only other use of this function is in acme code where the function
can never be called with a NULL 'key' pointer.
BUG/MINOR: h3: close connection on GOAWAY frame too long
GOAWAY frame contains a single stream ID varint. When decoding it,
ensure its length match the announced value in frame header. If not the
case, the connection must be closed with code FRAME_ERROR.
However, this fix is not complete as it did not detect H3 frame
truncated at the header boundary. This happens when one of the two
fields varint cannot be decoded and FIN is reported as well.
This patch corrects this by reporting a connection error H3_FRAME_ERROR
in this condition. This allows to quickly teardown the connection
instead of waiting for the stream timeout without any error.
BUG/MINOR: mux_quic: account data in conn fctl on early detach
The previous patch accounts unconsumed data when QCS is freed before
being remotely closed. The objective is to ensure to limit the
inconsistency in conn flow control between client and server.
In fact, this patch is incomplete as peer consumed level cannot be known
before receiving its final size. Thus, discrepancies in connection flow
control may still appear over time. The proper method is to delay QCS
release until remote close is performed. However, this is a significant
change which will be implemented in a future patch.
This must be backported up to 2.8, after a period of observation
BUG/MEDIUM: mux_quic: account discarded data in connection flow control
Read data not yet consumed by the upper stream layer are discarded when
a RESET_STREAM is received. This patch implements their accounting in
the connection flow control.
This feature is mandatory as it could result in discrepancies between
each peers for the measured connection flow control. If this grows too
big, the sender may be indefinitely blocked, forcing the opening of a
new connection.
This patch introduces a new function qcc_account_conn_fctl(), which is
used to factorize the code already present during STREAM frames
handling. This is now also called for RESET_STREAM frames.
This must be backported up to 2.8, after a period of observation.
BUG/MINOR: mux_quic: report conn error on RESET_STREAM fctl violation
When a RESET_STREAM is received, its announced final size must be taken
into account in the connection flow control.
This patch is a prerequisite prior to implement this accounting. It adds
the mandatory check of the final size field to ensure it does not
violate either the stream or the connection flow control.
In case of invalid value, the connection is closed with
FLOW_CONTROL_ERROR.
This must be backported up to 2.8, after a period of observation.
BUG/MINOR: mux_quic: extend FINAL_SIZE_ERROR detection on stream
On STREAM reception, a frame is rejected if final size is known but a
frame which exceeds it is received. Connection is closed with
FINAL_SIZE_ERROR code. This is also the case for a frame with FIN set
but shorter than the already received maximum offset.
This patch rewrites the condition so that the second check on frame with
FIN is always performed. Indeed, it does not depend on the known final
size status. A similar test is already performed as expected in the
RESET_STREAM frame handler.
Ensure QCC tasklet is woken up when a QCS is inserted in the purge list.
The objective is to prevent keeping QCS for too long in it.
Changes are only necessary when running outside of QCC tasklet. Thus new
wakeup are only inserted in receive handlers to avoid unnecessary
tasklet execution.
Prior to this patch, a purgeable QCS could have been kept virtually
indefinitely. This probably did not really happen as QCC tasklet is
frequently woken after frames handling.
This should be backported up to 3.2, after a period of observation.
BUG/MAJOR: mux-quic: prevent crash on QCS already set for purging
QCC connection has a purge list member. It contains QCS instances
considered as completed, whiches will be freed during the next tasklet
run. Such QCS are reported via qcs_is_completed() helper function.
QCS member <el_send> is used for purge list storage. It is also used for
send list, however an inactive QCS should not be present in the latter.
To enforce this rule, a BUG_ON() is present before purge list insertion
in qcs_close_remote(). This would also be triggered if QCS was already
inserted in the purge list before remote closing.
This BUG_ON() may be triggered when a QCS is scheduled for purge while
it still contains stom data to read. When the final read is performed,
crash will occur in qcs_close_remote(). This happens with detached QCS
only, as they are considered as completed even if only closed locally.
To prevent this situation, add an early return in qcs_close_remote() :
if QCS is already considered completed, do not change its state to fully
closed. As stated above, this only occur for detached QCS instances. The
main objective is to prevent the BUG_ON() crash while still preventing
double insert. As an added value, it guarantees that QCS state remains
constant once scheduled for purge.
Thanks to Brendon C. <bren@bren.win> for his detailled report on the
issue.
This must be backported up to 3.2, after a period of observation.
MINOR: ssl: accept integer group IDs in curve2name
Allow curve2name to convert integer TLS Supported Group identifiers
in addition to curve names. Register the converter with SMP_T_ANY to
preserve the input type, and accept only integer and string samples.
Return unknown IDs, or IDs without a name in the requested convention,
as decimal strings. Preserve the existing fallback for string inputs.
Document binary group extraction and extend regression coverage for
integer IDs, missing names, unknown IDs and values outside the TLS
identifier range. The build and converter regression test pass.
Add a string converter to normalize curve names to IANA, ANSI X9.62
or NIST notation. Match both the requested format and input names
case-insensitively, and resolve old ecdh_* aliases to canonical names.
Keep the original string unchanged when no curve matches or the
requested naming convention has no corresponding name. The converter
uses the curve table independently of the SSL library's available NIDs.
Document the supported formats and add regression coverage for curve
aliases, mixed-case formats and unchanged-string fallbacks. The build
and all three SSL curve regression tests pass.
MINOR: ssl: complete registered group names and library NID mappings
Add the missing IANA groups for SM2, ML-KEM and hybrid key exchanges.
Provide both AWS-LC and OpenSSL NID spellings for standalone ML-KEM
and use AWS-LC NIDs for the supported hybrid and Kyber draft groups.
Undefined NID macros remain unavailable through the existing V macro.
The mappings were checked with OpenSSL 3.5, OpenSSL 4 development
headers and AWS-LC 4.2.
MINOR: ssl: skip unavailable NIDs in curves2nid lookup
Curve table entries with NIDs undefined by the SSL library remain at -1
after initialization. Skip these entries during name lookup so an
unavailable mapping does not hide a later valid mapping for the same
name. This allows the table to contain both OpenSSL and AWS-LC NID
spellings without conditional compilation.
MINOR: ssl: split curve names into ANSI, IANA and NIST columns
Store ANSI X9.62 aliases separately from IANA and NIST names,
consolidating the duplicate P-192 and P-256 entries. Make curves2nid()
search all three columns and keep curveid2str() returning the existing
TLS group name.